diff --git a/plugins/antianqi/codex-harness-patterns/LICENSE b/plugins/antianqi/codex-harness-patterns/LICENSE new file mode 100644 index 0000000..87b9b48 --- /dev/null +++ b/plugins/antianqi/codex-harness-patterns/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of tracking or otherwise improving the Work, + but excludes communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for describing the origin of the Work and + reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may accept and charge a + fee for, acceptance of support, warranty, indemnity, or other + liability obligations and/or rights consistent with this License. + However, in accepting such obligations, You may act only on Your + own behalf and on Your sole responsibility, not on behalf of any + other Contributor, and only if You agree to indemnify, defend, + and hold each Contributor harmless for any liability incurred by, + or claims asserted against, such Contributor by reason of your + accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2026 antianqi + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/plugins/antianqi/codex-harness-patterns/OVERVIEW.md b/plugins/antianqi/codex-harness-patterns/OVERVIEW.md new file mode 100644 index 0000000..2cbe4dd --- /dev/null +++ b/plugins/antianqi/codex-harness-patterns/OVERVIEW.md @@ -0,0 +1,117 @@ +# codex-harness-patterns — Plugin 总览 + +> 最后更新:2026-08-26 · **v1.0.4** · **23 Skills** +> Plugin 覆盖率 ~90%+ + +## 一句话 + +> **23 个 skill** 把 mcode 从"灵机一动"的工作流,变成 Codex 团队在生产环境验证过的、**完整 agent 生命周期**的工程化体系: +> planning → decomposition → sub-agent parallelism → execution → state tracking → tool discovery → skill/plugin authoring → memory persistence → session branching + +## 23 Skill 一览(按生命周期) + +| # | Skill | 触发条件 | 一句话 | +|---|---|---|---| +| **规划与拆解** | | | | +| 1 | `plan-stream-emit` | 复杂任务 | 先出 `todowrite` 计划,等 ack 再动 | +| 2 | `parallel-fanout` | 任务可拆 2+ 独立子任务 | 显式 spawn,opt-in,fan-out + 聚合 | +| **子代理派发** | | | | +| 3 | `delegate-with-context` | 调 `task` 派发子 agent | 4-part 信封写进 `prompt`,`agent_name` 三选一 | +| 4 | `fork-context-decision` | 调 `task` 派发 | 选 `all`/`N`/`none`,把对应内容内联到 `prompt` | +| 5 | `subagent-family-tracking` | 派发了 sub-agent | 跟踪父子线程树 Open/Closed 状态 | +| **执行与状态** | | | | +| 6 | `background-task` | 命令预期 > 30s | `task(run_in_background)` + `task_query`/`task_output`/`task_stop`,或 `bash(run_in_background)` | +| 7 | `streaming-output-reader` | 长流式输出 | bounded chunk + summary,最多 3 次读 | +| 8 | `tool-output-budget` | 工具输出过大 | token-aware head/tail/marker 截断 | +| 9 | `world-state-tracking` | 任务长到丢线索 | 持久化 world state 文件,挺过 compact | +| 10 | `context-pressure-compact` | 多步长任务,context 满 | structured snapshot,64K retention | +| **目标与成本** | | | | +| 11 | `goal-persistence` | 非平凡任务开始 | 设 goal + drift-check + 跟到 compact | +| 12 | `goal-token-budgeting` | 设了 token_budget | 50%/80%/100% 报告,跑超就停 | +| 13 | `model-router` | 子任务 / 重复任务 | cheap/medium/main 思考框架 + session-level 路由 | +| **质量保证** | | | | +| 14 | `review-mode` | 子任务完成 | 切 critic,PASS / FIX / REDO 判决 | +| 15 | `completion-audit` | 说 "done" 前 | 派生需求 + 找证据 + 逐项验 | +| **容错与接力** | | | | +| 16 | `error-recovery-strategy` | 任何失败 | retry / switch / fallback / ask / skip | +| 17 | `retry-with-backoff` | 准备重试 | 显式策略:max/base/max/jitter/budget | +| 18 | `session-handoff` | 会话结束 | 写 handoff 文件,下次 30 秒接上 | +| **新(v1.0.0)·持久化与发现** | | | | +| 19 | `long-term-memory` | 设计跨 session 记忆 | Phase 1/2 extract+consolidate+citation,git baseline | +| 20 | `skill-auto-select` | 设计可被 agent 选择的 skill | 3 层匹配 + `$name` mention + 防歧义 | +| 21 | `plugin-author-helper` | 写 marketplace plugin | manifest 格式 + 3-layer sync + idempotency | +| 22 | `tool-discovery-pattern` | 设计可被 agent 发现的 tool | defer_loading + 7-type schema + tool_suggestion | +| 23 | `session-branch-fork` | 设计 session 分支/回滚/恢复 | paginated + lineage + CAS + bounded replay | + +## 完整生命周期图 + +``` +┌──────────────────────────────────────────────────────┐ +│ 完整 agent 生命周期 │ +└──────────────────────────────────────────────────────┘ + + 输入 + │ + ├─→ 【1. plan-stream-emit】 规划:出计划 + │ + ├─→ 【2. parallel-fanout】 拆解:fork 多个子任务 + │ │ + │ ├─→ 【3. delegate-with-context】 写简报 + 信封 + │ ├─→ 【4. fork-context-decision】 选 fork_turns + │ └─→ 【5. subagent-family-tracking】 跟踪父子树 + │ + ├─→ 【6. background-task】 后台化长命令 + ├─→ 【7. streaming-output-reader】 bounded chunk 读流 + ├─→ 【8. tool-output-budget】 截断大输出 + │ + ├─→ 【9. world-state-tracking】 持久化世界状态 + ├─→ 【10. context-pressure-compact】 context 满时 snapshot + │ + ├─→ 【11. goal-persistence】 设 goal,drift check + ├─→ 【12. goal-token-budgeting】 50/80/100% 报告 + ├─→ 【13. model-router】 选 model,分 cheap/medium/main + │ + ├─→ 【14. review-mode】 切 critic,出 verdict + ├─→ 【15. completion-audit】 派生需求 + 验证 + │ + ├─→ 【16. error-recovery-strategy】 失败:retry/switch/ask + ├─→ 【17. retry-with-backoff】 显式重试策略 + │ + ├─→ 【18. session-handoff】 写 handoff + │ + ├─→ 【19. long-term-memory】 跨 session 记忆 + │ │ + │ ├─→ 【20. skill-auto-select】 选 skill + │ ├─→ 【21. plugin-author-helper】 写 plugin + │ └─→ 【22. tool-discovery-pattern】 选 tool + │ + └─→ 【23. session-branch-fork】 分支 / 回滚 / 恢复 +``` + +## 与 Codex 源码的对应 + +每个 Skill 的 frontmatter `metadata.inspired-by` 字段指向具体的 Codex 源文件。 +Plugin 覆盖率 ~90%+ — 还有 ~12 个模式因安全/UI/voice/横向对比等原因被排除。 + +详见 `codex-harness-engineering/CATALOG.md`。 + +## 版本与里程碑 + +| 版本 | 阶段 | 发布 | +|---|---|---| +| v0.1.0 - v0.5.0 | 0 | 4→14 skills | +| v0.6.0 | 0 | 18 skills | +| v0.6.2 - v0.7.5 | 1-3 | 30+ 知识笔记 + 路线图 | +| **v1.0.0** | 4 | **23 skills(当前)** | + +## 设计原则 + +每个 Skill 都遵循同一结构: +- `description` 用 4 行格式(`USE WHEN / TRIGGER PHRASES / SKIP WHEN`,EN+中文) +- "When to use" + "When NOT to use" 显式声明 +- Process 编号步骤 +- Output contract 给出契约 +- Common pitfalls 列出反模式 +- Verification checklist 供自检 + +这一致性让 LLM 能可靠选择 Skill,让维护者能审计 Skill 质量。 diff --git a/plugins/antianqi/codex-harness-patterns/PR-STATUS.md b/plugins/antianqi/codex-harness-patterns/PR-STATUS.md new file mode 100644 index 0000000..3904b4a --- /dev/null +++ b/plugins/antianqi/codex-harness-patterns/PR-STATUS.md @@ -0,0 +1,69 @@ +# PR 状态 + +> 最后更新:2026-08-26 + +## 当前状态 + +| 项 | 值 | +|---|---| +| **PR 编号** | [#18](https://github.com/MiniMax-AI/MiniMax-Code-Plugins/pull/18) | +| **PR URL** | https://github.com/MiniMax-AI/MiniMax-Code-Plugins/pull/18 | +| **目标分支** | MiniMax-AI/MiniMax-Code-Plugins:main | +| **来源分支** | antianqi/MiniMax-Code-Plugins-1:main | +| **状态** | OPEN — review: CHANGES_REQUESTED by hetaoBackend(round 1 + round 2,正在修复) | +| **当前版本** | v1.0.4 (patch: 5 Skills rewritten against mcode 0.2.4 actual `task` / `bash` schema; 23-Skill frontmatter static check added) | +| **前一版本** | v1.0.3 (patch: 4 Skill bodies corrected per reviewer #2 round 1) | +| **Plugin size** | 23 Skills(在 64 上限内)+ 1 manifest + 1 README + 1 LICENSE | +| **变更** | +xxx / -xxx 行,x 文件 | +| **静态 CI** | ⚠️ [code]smith: SKIPPED | +| **CodeQL** | 待扫 | +| **官方 review** | ⚠️ hetaoBackend (COLLABORATOR): round 1 = 3 issues, round 2 = 6 specific points under the same PR; round 2 in flight | + +## 已知 reviewer issues(2026-08-25 收到 round 1,2026-08-26 收到 round 2) + +来自 hetaoBackend 评审。 + +### Issue 1 · 文档版本不一致 +- **现状**:v1.0.2 manifest + OVERVIEW 跟历史 PR-STATUS.md / README changelog 不一致 +- **修复**:本文件已重写,统一为 v1.0.3 / 23 Skills(2026-08-26) + +### Issue 2 · Codex-only 工具参数(round 1)+ 不准确的 mcode 适配(round 2) +- **Round 1 现状**:5 个 Skill 用了 mcode 不存在的 Codex 工具参数: + - `fork_turns` / `subagent=...` / `reasoning_effort` 等 +- **Round 1 修复**: + - 1f4530c2:SKILL.md 重写,移除 Codex-only 参数,标注为 "Codex 习惯 + mcode 工具的等价为 ..." 注释 + - 72952c9(v1.0.3 amend):example 改为 mcode 实际 `task(agent_name=...)` 调用形式;同步删除 `assets/agents//agent.md` 这种 host-internal 路径(不是 public contract);该路径由 dev-only 的 `sub-agent types claimed in Skills are present in the local mcode 0.2.4 install` test 在 SKILL 维护者本机的 mcode install 上 best-effort 验证 +- **Round 2 现状**(在 `7de6d539` 上 reviewer 提出 6 个具体点): + 1. `fork-context-decision` 有重复 frontmatter block(round 1 未修干净) + 2. `fork-context-decision` example 仍含 `history=...` PLACEHOLDER + 3. `background-task` 仍含 `bash(task_name=..., run_in_background=true)` + `bash(action="kill")` 伪代码 + 4. `delegate-with-context` / `parallel-fanout` 把 task 调用形状留给读者 + 5. 必须按 mcode 实际契约重写 **或** 明确标 host-independent Codex 伪代码 + 6. 加 23-Skill frontmatter 静态检查 +- **Round 2 修复**(v1.0.4 amend): + - 通过读 `C:\Users\Administrator\.minimax-code\node_modules\@minimax-ai\code\cli.js` 直接拿到 mcode 0.2.4 `task` / `bash` / `task_query` / `task_output` / `task_stop` 的实际 schema(`cli.js:B6c` / `cli.js:xza` / `cli.js:iRt`) + - 5 个 Skill 全部用真实 mcode API 重写: + - `task(description, prompt, agent_name, run_in_background?)` — 4 个 params 都是真实 mcode 字段 + - `agent_name` 是 canonical,`agent_name=` 是运行时别名(`cli.js:j6c` normaliser) + - `mavis` 是 root agent,不是 sub-agent(没有 `agent.md` manifest);`agent_name` 只能从 `{explore, worker, verifier}` 选 + - mcode 0.2.4 没有 `history=` / `fork_turns=` / `context_size=` — 3 fork 模式通过在 `prompt` 里内联多少 prior turns 来表达 + - mcode 0.2.4 没有 per-call `model_config_id` — 模型选择是 session-level,`model-router` 重写为思考框架 + spawn gate + - 后台任务:`task(run_in_background: true)` 返回 `task_id`,用 `task_query` / `task_output` / `task_stop` 管理 + - 后台 shell:`bash(command, run_in_background: true)`,杀掉靠 host job-control API(Windows `Stop-Process`,POSIX `kill`)走**foreground** `bash` 调用 + - 新增 `test/codex-harness-patterns.test.mjs`:23-Skill frontmatter 静态检查 + 5 个 task-touching Skills 的 mcode schema pinning + +### Issue 3 · plugin-authoring / memory 写行为未声明 host 边界 +- **现状**:`plugin-author-helper` 和 `long-term-memory` 描述了网络/安装/写文件行为,未声明需 user 确认 +- **修复**(commit 6f1a6150):每个描述副作用的章节加 "需要 user 确认 / 需要 plugin runtime 支持" 前缀 + +## 修复 commit 历史 + +| commit | 内容 | +|---|---| +| 5b7f1a8c | v1.0.2:README 4 段披露 | +| 1f4530c2 | reviewer #2 round 1(伪代码 + 适配说明) | +| 6f1a6150 | reviewer #3(plugin-author-helper / long-term-memory host 边界) | +| 72952c9 | v1.0.3 amend:replayer #2 round 1(实际 `task(agent_name=...)` 语法) | +| a9f80c3 | docs:version references 1.0.2 → 1.0.3 | +| aa77b1c | fix:hardcoded path + stale frontmatter reference 清理 | +| (v1.0.4) | reviewer #2 round 2:5 Skills 按 mcode 0.2.4 真实 `task` / `bash` schema 重写;新增 23-Skill frontmatter 静态检查 | diff --git a/plugins/antianqi/codex-harness-patterns/README.md b/plugins/antianqi/codex-harness-patterns/README.md new file mode 100644 index 0000000..6436959 --- /dev/null +++ b/plugins/antianqi/codex-harness-patterns/README.md @@ -0,0 +1,662 @@ +# codex-harness-patterns + +A focused collection of Skills distilled from the **OpenAI Codex harness v0.149.0** execution +model (`codex-rs/core/`). These Skills teach a MiniMax Code agent how to survive long-running +multi-step tasks without losing focus, blowing its token budget, stalling on serial work, +shipping unverified changes, burning context on bad sub-agent briefs, drifting from the +original goal, paying main-model prices for cheap-model work, losing track of which +sub-agent is doing what, failing on transient errors without a budget, reading streaming +output without filling context, or losing work at session end. + +## v1.0.4 changelog (this release) + +> **类型**:patch · **Skill 主体修正 (round 2)** · 5 个 Skill 按 mcode 0.2.4 真实 `task` / `bash` schema 重写;新增 23-Skill frontmatter 静态检查 + +### Fixed (PR #18 reviewer #2 round 2) + +- `fork-context-decision` `0.2.0 → 0.3.0`:reviewer 提出的 6 点全部 close + - (1) 重复 frontmatter block(round 1 残留)→ 重新组装为单一 frontmatter + - (2) `history=N` PLACEHOLDER 移除 — mcode 0.2.4 `task` 工具**没有** context-sharing 参数;3 fork 模式(`all` / `N` / `none`)现在通过在 `prompt` 字段内联多少 prior turns 来表达 + - (3) `agent_name=` 换成 canonical `agent_name=`(`cli.js:B6c` 严格 validator 只接受 `agent_name`, `subagent_type=` 是运行时 alias(`cli.js:j6c` normaliser)) + - (4) `brief=` 换成 canonical `prompt=` + - (5) `mavis` 从 subagent 列表移除(它是 root agent,host-internal layout 不在 public contract 内;不能用作 `agent_name`) +- `delegate-with-context` `1.1.0 → 1.2.0`:同上 4 个 API 修正;4-part 信封现在写进 `prompt` 字段(不再有 `brief=` 概念) +- `parallel-fanout` `1.1.0 → 1.2.0`:同上;每个 sub-task 独立 `task()` 调用,host 的 `buffer-unordered` 默认 8 +- `model-router` `0.3.3 → 0.4.0`:**删除** v0.3.3 "MiniMax Code's `task` tool accepts `model_config_id` directly" 错误断言 — mcode 0.2.4 `task` 工具不接受任何 model 字段(`cli.js:B6c` 严格 validator);模型选择是 **session-level**;Skill 重新定位为 cheap/medium/main 思考框架 + spawn gate(不要为 cheap 任务 spawn sub-agent) +- `background-task` `0.1.2 → 0.2.0`:重新组织为 mcode 0.2.4 的两层后端 + - sub-agent 后台:`task(..., run_in_background: true)` 返回 `task_id`;管理用 `task_query(task_id)` / `task_output(task_id, offset?)` / `task_stop(task_id, reason?)`(`cli.js` canonical schema) + - shell 后台:`bash(command, run_in_background: true)` — mcode `bash` 工具的真实 schema(`cli.js:xza`):`command` / `timeout?` / `run_in_background?`;**没有** `task_name` / `action="kill"`(这俩是 Codex 专属,reviewer 反复要求去掉) + - 杀掉 shell 后台任务用 host job-control API(Windows `Stop-Process -Id`,POSIX `kill `)走**foreground** `bash` 调用,不假装 `bash(action="kill")` 存在 + +### Added (PR #18 reviewer #2 round 2 point 6) + +- **23-Skill frontmatter 静态检查**:`test/codex-harness-patterns.test.mjs`(被 `node --test` 自动发现) + - 23 个 `SKILL.md` 全部要求**单一** frontmatter block(开头 `---\n`,闭合 `\n---\n`,中间无 stray `---`) + - frontmatter 解析为结构化对象,verify 必填字段:`name`(=目录名)/ `description`(≤1024 字符)/ `license=Apache-2.0` / `metadata.author=antianqi` / `metadata.version` 非空 + - body 顶层无 `author:` / `version:` 重复(避免 round 1 reviewer 提到的"重复 frontmatter block"再发生) + - **mcode 0.2.4 schema pinning**:对 5 个 task-touching Skills,所有 code block 里的 `task(...)` / `bash(...)` 调用**必须**用 canonical 参数(`description` / `prompt` / `agent_name` / `run_in_background` / `command` / `timeout`),禁止 `subagent_type=` / `subagent=` / `agent_type=` / `fork_turns=` / `brief=` / `history=` / `model_config_id=` / `bash(task_name=...)` / `bash(action="kill")`(reviewer 提的全部 9 个 placeholder 都被 fail-closed) + - `background-task` 必须显式 demo `task_query` / `task_output` / `task_stop`(不然跑不动 `run_in_background=true` 返回的 task_id) + - 跑分:`node --test test/codex-harness-patterns.test.mjs` → **27 pass, 0 fail** + +### 验证方法(可复现) + +```bash +# 把 mcode 0.2.4 真实 schema 拿出来对照 +grep -A2 'name:"task",executionMode' \ + C:/Users/Administrator/.minimax-code/node_modules/@minimax-ai/code/cli.js +# → 4 个 param: description / prompt / agent_name / run_in_background + +# 跑 frontmatter + schema pinning 测试 +node --test test/codex-harness-patterns.test.mjs +# → 27 pass, 0 fail + +# 跑 plugin validator(需要 core.autocrlf=false 才能跑过 Windows CRLF) +node scripts/validate.mjs +# → OK plugin antianqi/codex-harness-patterns +``` + +### Compliance + +- 4 段独立披露(no credentials / no network / no telemetry / no third-party services)— 不变 +- Skill-only plugin(无 `mcp.json` / 无 `package.json` / 0 npm 依赖)— 不变 +- 跨平台 path 解析 — 不变,这次 review 没有触发新硬编码 +- 23 个 Skill 的 frontmatter 唯一性(无重复 `author:` / `version:` block)— 现在由 `test/codex-harness-patterns.test.mjs` 守门 + +### Not changed + +- 23 skill 整体布局与触发条件 +- 4 段独立披露格式 +- License +- 19 个非 task-touching Skills 的 body(v1.0.4 没改 `completion-audit` / `context-pressure-compact` / `error-recovery-strategy` / `goal-persistence` / `goal-token-budgeting` / `long-term-memory` / `plan-stream-emit` / `plugin-author-helper` / `retry-with-backoff` / `review-mode` / `session-branch-fork` / `session-handoff` / `skill-auto-select` / `streaming-output-reader` / `subagent-family-tracking` / `tool-discovery-pattern` / `tool-output-budget` / `world-state-tracking`) + +## v1.0.3 changelog (previous) + +> **类型**:patch · **Skill 主体修正 (round 1)** · 4 个 Skill 的 mcode 适配注释从"伪代码"升级到 mcode 实际 `task(agent_name=...)` 语法;修复 v1.0.3.1 草稿的 frontmatter 结构损坏 + +### Fixed + +- `fork-context-decision` `0.1.2 → 0.2.0`:example 调用从 "Codex-style `task(subagent=..., fork_turns=...)` 伪代码" 改为 mcode 实际 `task(agent_name=..., brief=..., history=...)` 语法;清掉 v1.0.3.1 草稿的重复 `metadata:` 块、孤立 YAML、body 中重复 `# Fork Context Decision` 标题、stray `---` 分隔符(YAML 解析拿到错的 version 字段) +- `delegate-with-context` `1.0.2 → 1.1.0`:同上,example 改用 `agent_name="explore"`;删除"工具集是 yaml 写死"这种 host 内部实现细节的断言 +- `parallel-fanout` `1.0.2 → 1.1.0`:同上;删除"Reads each agent's tool whitelist from mcode assets/agents/<name>/agent.md"这种引用不存在路径的断言 +- `model-router` `0.3.2 → 0.3.3`:恢复 portable 3-tier rubric;删除"agent_type 已经隐含 tier"这种依赖不存在 yaml 配置的强断言;`reasoning_effort` 显式标注为 Codex-only、mcode 不暴露 + +### Compliance + +- 4 段独立披露(no credentials / no network / no telemetry / no third-party services)— v1.0.2 已加,本版本未改 +- 跨平台 path 解析 — `assets/agents//agent.md` 这种相对路径断言已删除 +- Skill-only plugin(无 `mcp.json` / 无 `package.json` / 0 npm 依赖)— 不变 +- 一个 commit 一个 fix — 本次 amend 把 4 个 Skill 的修复合到同一个 v1.0.3 commit,因为它们都是 reviewer issue 2 的同一根因(Codex-style 参数名 → mcode-actual 参数名) + +### Not changed + +- 23 skill 整体布局与触发条件 +- 4 段独立披露格式 +- License +- README 的其他部分 + +## v1.0.2 changelog (previous) + +> **类型**:patch · **Skill 主体不变** · README 加 4 段独立披露(满足 mcode plugin 提交规范) + +### Added + +- README 新增 `Disclosure (per mcode plugin convention)` 一节,4 段独立披露: + - **No credentials** — 不读 / 存 / 传 / 请求任何凭据 + - **No network** — 任何出站调用 / socket / 自动更新 + - **No telemetry** — 任何自身指标 / trace / event / log + - **No third-party services** — 不绑 MCP / npm / 原生 binary / 外部 runtime +- PR #18 body 改为 `Design compliance / Validation / Test evidence` 三段式 + +### Compliance + +- mcode `~/.minimax/memory/user.md` 第 36-42 行规定的 4 段披露格式 — 现在 README 显式列出 +- 跨平台 path 解析 — 已验证无硬编码 `C:\` / `D:\` / `/Users/` / `/home/` +- Skill-only plugin (无 `mcp.json` / 无 `package.json` / 0 npm 依赖) — 已声明 +- 一个 commit 一个 plugin 范围 — 此次只改 README + +### Not changed + +- 23 skill 主体(版本号不变) +- 23 skill frontmatter +- plugin.json 其他字段 +- License + +## v1.0.1 changelog (previous) + +> **类型**:patch · **Skill 主体不变** · 文档收尾(OVERVIEW.md / STATUS.md 全面刷新 + PR #18 title 更新) + +### Added + +- `OVERVIEW.md` 全面刷新 — 23 Skills 按生命周期分组 + 完整生命周期图 +- `PR-STATUS.md` 同步到 v1.0.0 状态 +- PR #18 title 更新为 "v1.0.0 — 23 Skills covering complete agent lifecycle" +- PR #18 body 全面重写 — 23 Skills 表格 + v0.1.0 - v1.0.0 完整 changelog + +### Not changed + +- 23 skills 主体(版本号全部不变) +- 23 skills frontmatter + +## v1.0.0 changelog (previous) 🎉 + +> **类型**:**MAJOR** · **Plugin 1.0 里程碑** · 5 个新 skill + 完整生命周期覆盖 + +### 🎉 v1.0 里程碑 + +Plugin 现在覆盖 Codex agent 的**完整生命周期**: +``` +planning → decomposition → sub-agent parallelism → execution → +state tracking → tool discovery → skill/plugin authoring → +memory persistence → session branching +``` + +**23 个 Skill**(从 18 增加到 23),Plugin 覆盖率 **~90%+**。 + +### Added — 5 个新 Skill + +| # | Skill | 用途 | 灵感来源 | +|---|---|---|---| +| 19 | `long-term-memory` | 跨 session 长期记忆设计(Phase 1/2 提取 + 合并 + citation) | `codex-rs/memories/` | +| 20 | `skill-auto-select` | 设计可被 LLM 可靠选择的 skill(三层匹配 + mention 语法) | `codex-rs/skills/` | +| 21 | `plugin-author-helper` | 写 marketplace Plugin(manifest 格式 + 3-layer sync + idempotency) | `codex-rs/core-plugins/` | +| 22 | `tool-discovery-pattern` | 设计可被 agent 发现的 tool(defer_loading + 7-type schema) | `codex-rs/tools/` | +| 23 | `session-branch-fork` | session 分支 / 回滚 / 恢复(paginated + lineage + ModelContext) | `codex-rs/thread-store/` | + +### Coverage journey + +- **v0.6.2 (开始)**:60% 覆盖 +- **v0.7.0 (阶段 1 完成)**:78% +- **v0.7.5 (阶段 3 完成)**:88% +- **v1.0.0 (阶段 4 完成)**:~90%+ + +### Not changed + +- 18 个原有 skill 主体不变,版本号不变 +- 原有 18 skill 的 frontmatter / 触发条件不变 + +## v0.7.5 changelog (previous) + +> **类型**:patch · **Skill 主体不变** · 研究状态更新(阶段 3 边角 crate 收口) + +### Added + +- 1 篇新知识笔记(对 `apply-patch` / `context-fragments` / `mcp-server` / `app-server-daemon`): + - `P-148-156-edge-crates.md`(5KB)— Apply Patch Lark grammar + Context Fragments + MCP Server + App Server Daemon +- CATALOG 状态:🟢 108→112 / 🟡 3→3 + +### Key insight + +**Codex 边角能力**: +- **Apply Patch** — 自有 Lark grammar,lenient 解析 +- **Context Fragments** — 带 metadata 的 context 片段(`AnnotatedContent`) +- **MCP Server** — Codex 自身可作为 MCP tool(`codex_tool_runner`) +- **App Server Daemon** — 自我管理 binary + SHA256 + self-update loop + +### Not changed + +- 18 skill 主体(版本号全部不变) + +## v0.7.4 changelog (previous) + +> **类型**:patch · **Skill 主体不变** · 研究状态更新(阶段 2 周 8) + +### Added + +- 1 篇新知识笔记(对 `codex-rs/protocol/src/` 关键未读模块): + - `P-128-protocol-capabilities-user-input.md`(7KB)— Capabilities + UserInput + OpenAI Models + Config Types + Permission Intersection +- CATALOG 状态:🟢 100→108 / 🟡 3→3 + +### Key insight + +**Codex 协议层模式**: +- **跨 4 边界共享**(core / TUI / app-server / SDK) — 字段默认必须保留 +- **TS + JsonSchema 双重 derive** — 自动生成 TypeScript + JSON Schema +- **`ts(export_to = "v2/")` 版本化** — 协议分版本 +- **Two-stage Parse** — API 变化时自动 fallback +- **Intersection** — 权限 / 配置用集合论组合 +- **deprecated 但保留** — 向后兼容 + +## v0.7.3 changelog (previous) + +> **类型**:patch · **Skill 主体不变** · 研究状态更新(阶段 2 周 7) + +### Added + +- 2 篇新知识笔记(对 `codex-rs/rollout/` + `codex-rs/models-manager/` 深读): + - `P-117-127-rollout-persistence.md`(4KB)— zstd 压缩 + ReverseJsonlScanner + RolloutReferenceIndex + - `P-128-133-models-manager.md`(5KB)— ModelsEndpointClient trait + 5min 文件 cache + 1177 行 models.json +- CATALOG 状态变更:`P-117/118/119/120 + P-128/129/130/132` 🟡→🟢(8 个) + +### Key insight + +- **zstd + 反向扫描** — 冷 rollout 自动压缩,反向读只取末段 +- **RolloutReferenceIndex** — 不读文件就能回答"谁引用了我" +- **models.json 1177 行** — 完整 capability matrix(input_modalities/truncation_policy/prefer_websockets/...) +- **ModelsEndpointClient trait** — 多 provider 抽象 + +### Not changed + +- 18 skill 主体(版本号全部不变) +- 18 skill frontmatter + +## v0.7.2 changelog (previous) + +> **类型**:patch · **Skill 主体不变** · 研究状态更新(阶段 2 周 6) + +### Added + +- 4 篇新知识笔记(对 `codex-rs/tools/` 25+ 文件深读): + - `P-107-108-tool-discovery-search.md`(5KB)— DiscoverableTool 二维分类 + `defer_loading` 搜索结果 + - `P-109-111-dynamic-mcp-tool.md`(6KB)— 简单 vs 复杂适配器 + OpenAI 协议补全 + - `P-112-113-plugin-install-responses-api.md`(5KB)— Tool suggestion 审批 + Responses API 5 个类型 + - `P-114-116-json-schema-image-response-history.md`(5KB)— 7 type subset + BTreeMap 稳定输出 +- CATALOG 状态变更:`P-107/108/109/110/111/112/113/114` 🟡→🟢(8 个) +- **🟢 首次突破 100 个已掌握模式** + +### Key insight + +**Tool 运行时全栈**: +- **Discovery** — DiscoverableTool(Connector/Plugin × Install/Enable 二维) +- **Search** — `defer_loading` 模式(搜索结果只含 name+description,schema 延迟加载) +- **Dynamic vs MCP** — 简单透传 vs 复杂 schema 补全(`properties` 必填兜底) +- **Install** — `request_plugin_install` 走 `tool_suggestion` 审批类型 +- **JSON Schema** — OpenAI Structured Outputs 子集(7 type + 3 composition) +- **Responses API** — 5 个类型(Function / Custom / Namespace + 嵌套) + +**Plugin 不直接涉及 tool**,但**借鉴模式**: +- "简单 vs 复杂适配器" — Plugin manifest 也是 +- "defer loading" — Skill description 应当简洁 +- "OpenAI 协议补全" — 跟 3rd-party 兼容 +- "Tool suggestion 审批" — 任何"加新能力"都该走审批 + +### Not changed + +- 18 skill 主体(版本号全部不变) +- 18 skill frontmatter + +### Roadmap progress + +| 阶段 | 状态 | 覆盖率 | +|---|---|---| +| 0 · 错判修正 | ✅ v0.6.2 | 60%→62% | +| 1 · 5 大核心 | ✅ v0.7.0 | 62%→78% | +| 2 · 周 5 agent + session | ✅ v0.7.1 | 78%→80% | +| **2 · 周 6 tools/** | ✅ **v0.7.2** | **80%→83%** | +| 2 · 周 7 rollout/ + models-manager/ | ⏳ 下一步 | — | + +## v0.7.1 changelog (previous) + +> **类型**:patch · **Skill 主体不变** · 研究状态更新(阶段 2 周 5) + +### Added + +- 4 篇新知识笔记(对 `codex-rs/core/src/agent/` + `codex-rs/core/src/{session,context_manager}/` 关键未读模块深读): + - `P-134-138-agent-registry.md`(7KB)— AgentRegistry + Mutex/Atomic 双层 + "Customize OR reduce, never REPLACE" 角色覆盖 + - `P-140-142-context-manager.md`(5KB)— ContextManager `Arc` CoW + history_version + reference snapshot diff + - `P-158-turn-suspension.md`(6KB)— 完整 9 步 suspend 流程 + 7 大设计原则 + - `P-157-162-session-infrastructure.md`(5KB)— Rollout budget / MCP refresh / Input queue / Elicitation / Time reminder +- CATALOG 状态变更:`P-134 / P-137 / P-138 / P-140-142 / P-157-160` 🟡→🟢(10 个) + +### Key insight + +**Codex session 中断的完整生命周期** = `Op::SuspendTurnAndShutdown` → 9 步 suspend 流程 → `Op::RecoverTurn` 恢复。 +关键设计: +- Snapshot vs Seal(descendants 检查接受 best-effort) +- Flush Before Cancel(持久化失败就让原 turn 继续) +- No Terminal Event(故意给 RecoverTurn 留恢复口) +- Event After Writer Closed(防并发写顺序) +- Handoff Drops State(pending input 不持久化) + +### Not changed + +- 18 skill 主体(版本号全部不变) +- 18 skill frontmatter + +### Roadmap progress + +| 阶段 | 状态 | 覆盖率 | +|---|---|---| +| 0 · 错判修正 | ✅ v0.6.2 | 60%→62% | +| 1 · 5 大核心 | ✅ v0.7.0 | 62%→78% | +| **2 · 周 5 agent + session** | ✅ **v0.7.1** | **78%→80%** | +| 2 · 周 6 tools/ | ⏳ 下一步 | — | + +## v0.7.0 changelog (previous) + +> **类型**:**minor** · **Plugin 里程碑** · 阶段 1(5 大核心 crate)整圈收口 · **Skill 主体不变** + +### Added + +- 4 篇新知识笔记: + - `P-93-95-plugin-loader-marketplace-manifest.md`(7KB)— Plugin 运行时 + marketplace + manifest 三件套 + - `P-99-plugin-startup-sync.md`(4KB)— 3 层 fallback + lock file + SHA 缓存 + - `P-164-prompts-compact.md`(3KB)— compact 5 个 must-have + - `P-165-prompts-goals.md`(5KB)— 4 大设计原则(防 prompt injection / 防偷工减料) + - `P-166-168-prompts-permissions-realtime-review.md`(8KB)— 3 套 permissions + 3 套 realtime + 3 套 review +- CATALOG 状态变更:`P-93/94/95/99 + P-164/165/166/167/168` 全部 🟡→🟢 + +### 阶段 1 整圈收口 + +| 周 | crate | 状态 | 发布 | +|---|---|---|---| +| 1 | `codex-rs/memories/` 21 文件 | ✅ | v0.6.3 | +| 2 | `codex-rs/skills/` 10+ 文件 | ✅ | v0.6.4 | +| 3 | `codex-rs/thread-store/` 40+ 文件 | ✅ | v0.6.5 | +| 4 | `codex-rs/core-plugins/` 60+ 文件 + `codex-rs/prompts/` 4 套 | ✅ | **v0.7.0** | + +**5 大核心 crate 全部完成**: +- ✅ memories (跨 session 长期记忆) +- ✅ skills (Plugin 直接对应物) +- ✅ thread-store (完整 session 持久化) +- ✅ core-plugins (Plugin 运行时) +- ✅ prompts (4 套 prompt 模板) + +**Plugin 覆盖率**:**72% → 78%**(+6%,阶段 1 净增 16%) + +### Not changed + +- 18 skill 主体(版本号全部不变) +- 18 skill frontmatter + +### Next + +- 阶段 2(周 5-8):core/agent/ + core/session/ + tools/ + rollout/ + models-manager/ + protocol/ +- 阶段 3(周 9-10):边角 crate 收口 +- 阶段 4(周 11-12):5 个新 skill + Plugin v1.0 + +## v0.6.5 changelog (previous) + +> **类型**:patch · **Skill 主体不变**(18 个 0.x.y 版本号不变) · 研究状态更新(阶段 1 周 3 完成) + +### Added + +- 6 篇新知识笔记(对 `codex-rs/thread-store/` 40+ 文件深读 — **完整 session 持久化层**): + - `P-67-thread-sections.md`(4KB)— section 管理 + operation-tagged state access + - `P-68-thread-projects.md`(4KB)— projects + `Option>` 三态 + idempotency key + - `P-69-70-queue-search.md`(5KB)— queue change-based polling + search snippet + - `P-71-72-migration-lineage.md`(6KB)— Legacy→Paginated migration + bounded subagent replay + - `P-76-model-context-reconstruction.md`(4KB)— ReverseJsonlScanner + bounded replay + - `P-77-thread-history-segmentation.md`(5KB)— 跨 segment 双向 cursor + 防溢出 +- CATALOG 状态变更:`P-67 / P-68 / P-69 / P-70 / P-71 / P-72 / P-76 / P-77` 全部从 🟡→🟢 +- **CATALOG 状态首次全清零**:`🟡 1→0` —— 所有 🟡 模式都进入 🟢 + +### Key insight + +`thread-store/` 是 Codex session 持久化的**完整基础设施**: +- Sections / Projects / Queue / Search 提供**管理面** +- Rollout Lineage / Migration / ModelContext reconstruction 提供**历史面** +- ThreadStore trait + `LocalThreadStore` + `InMemoryThreadStore` 提供**抽象层** + +新 skill `session-branch-fork`(阶段 4)的核心参考全部在这里。 + +### Not changed + +- 18 skill 主体(版本号全部不变) +- 18 skill frontmatter + +### Roadmap progress + +| 阶段 | 状态 | 覆盖率 | +|---|---|---| +| 0 · 错判修正 | ✅ v0.6.2 | 60%→62% | +| 1 · 周 1 memories/ | ✅ v0.6.3 | 62%→64% | +| 1 · 周 2 skills/ | ✅ v0.6.4 | 64%→66% | +| **1 · 周 3 thread-store/** | ✅ **v0.6.5** | **66%→72%** | +| 1 · 周 4 core-plugins/ + prompts/ | ⏳ 下一步 | — | + +## v0.6.4 changelog (previous) + +> **类型**:patch · **Skill 主体不变**(18 个 0.x.y 版本号不变) · 研究状态更新(阶段 1 周 2 完成) + +### Added + +- 6 篇新知识笔记(对 `codex-rs/skills/` 10+ 文件深读 — **Plugin 直接对应物**): + - `P-85-skill-selection-algorithm.md`(6KB)— 显式 + 隐式 selection,`O(T + (N_s + N_t) * S)` 复杂度,三层匹配 + - `P-86-skill-loading.md`(6KB)— 加载抽象 + 缓存 + system skills 嵌入式分发 + - `P-87-skill-frontmatter-parser.md`(6KB)— frontmatter 解析 + `repair_frontmatter_scalar_fields` 容错 + - `P-88-skill-mention-extractor.md`(5KB)— `$skill-name` + `[$name](path)` 链接语法 + - `P-89-implicit-skill-invocation.md`(5KB)— shell 命令隐式调用检测 + 平台感知分词 + - `P-92-skill-metadata-model.md`(6KB)— 完整 11 字段 metadata + 双形态抽象 +- CATALOG 状态变更:`P-85 / P-86 / P-87 / P-88 / P-89 / P-92` 全部从 🟡→🟢 + +### Key insight + +Codex skills 系统的 selection/loading/parser/mentions/model 5 个核心模块**直接对应我们 Plugin 的结构**。 +Plugin 当前的"skill 选取"能力**远弱于** Codex skills/ — 这是新 skill `skill-auto-select` 的来源(阶段 4 计划)。 + +### Not changed + +- 18 skill 主体(版本号全部不变) +- 18 skill frontmatter + +### Roadmap progress + +| 阶段 | 状态 | 覆盖率 | +|---|---|---| +| 0 · 错判修正 | ✅ v0.6.2 | 60%→62% | +| 1 · 周 1 memories/ | ✅ v0.6.3 | 62%→64% | +| **1 · 周 2 skills/** | ✅ **v0.6.4** | **64%→66%** | +| 1 · 周 3 thread-store/ | ⏳ 下一步 | — | + +## v0.6.3 changelog (previous) + +> **类型**:patch · **Skill 主体不变**(18 个 0.x.y 版本号不变) · 研究状态更新(阶段 1 周 1 完成) + +### Added + +- 4 篇新知识笔记(对 `codex-rs/memories/` 21 文件深读): + - `P-78-memory-phase1.md`(6KB)— Memory Phase 1:per-rollout extraction,JSON schema 强制 + `buffer_unordered` 并发 + 4 类高信噪比判定 + - `P-79-memory-phase2.md`(8KB)— Memory Phase 2:global consolidation,10 步线性流程 + 全局单 lock + 内部 consolidation agent 锁死配置 + - `P-80-memory-citation.md`(4KB)— MemoryCitation 协议 + `` / `` 解析 + - `P-84-memory-workspace-git.md`(8KB)— Memory workspace + git baseline 模式 +- CATALOG 状态变更:`P-78 / P-79 / P-80 / P-84` 全部从 🟡→🟢 +- CATALOG §9.2 memory 系统状态列加上"状态"字段 + +### Not changed + +- 18 skill 主体(版本号全部不变) +- 18 skill frontmatter +- Plugin 主合约 / 触发条件 / 输出契约 + +### Roadmap progress + +| 阶段 | 状态 | 覆盖率 | +|---|---|---| +| 0 · 错判修正 | ✅ v0.6.2 完成 | 60%→62% | +| 1 · 5 大核心 crate | 🟢 周 1 完成 | 62%→64% | +| 1 · 周 2 skills/ | ⏳ 下一步 | — | + +## v0.6.2 changelog (previous) + +> **类型**:patch · **Skill 主体不变**(18 个 0.x.y 版本号不变) · **元数据 + 文档更新** + +### Added + +- **CATALOG §7 修正**:把之前标 ⛔"范围外"的 4 个 session/thread 模式(`P-49 Fork` / `P-50 Rollback` / + `P-51 Recover` / `P-52 History Mode`)重新归类为 🟡"待深读" — 它们的真实实现位置是 + `codex-rs/thread-store/`(40+ 文件,完整 fork/revert/recover/segmentation 实现),不是"范围外"。 +- **CATALOG §8 修正**:把 `P-63 Skills runtime` 和 `P-64 Memory system` 从 ❌"不在 4 个重点" + 改为 🟡"待深读" — 它们是 Codex 跨 session 长期记忆和 skill runtime 的核心实现,**直接对应 + 我们 Plugin 自身结构**(`codex-rs/skills/` + `codex-rs/memories/`)。 +- **CATALOG §9 新增**:2026-08-24 复盘发现 ~100 个未研究模式草案,挑选 50+ 高价值列入。最高价值: + - ⭐⭐⭐⭐⭐ `memories/` Phase 1/2(per-rollout extraction + global consolidation) + - ⭐⭐⭐⭐⭐ `skills/` 完整 runtime(selection / loading / parser / mentions) + - ⭐⭐⭐⭐ `core-plugins/` marketplace 运行时 + - ⭐⭐⭐⭐ `tools/` discovery / search / dynamic tool + - ⭐⭐⭐⭐ `prompts/` 完整 4 套 prompt 模板 + +### Documentation + +- 新增 `research-log/2026-08-24-resurvey-findings.md`(25KB) — 完整复盘报告 +- 新增 `RESEARCH-ROADMAP.md`(12KB) — 2-3 月系统性补完计划(阶段 0-4) +- 新增 6 篇纠错笔记(`knowledge/P-{49,50,51,52,63,64}-*.md`)— 详述错判反思 + 实际代码位置 + +### Honest acknowledgment + +这次复盘揭示了 Plugin 实际**只覆盖了 Codex 模式库的 ~60%**。18 skill 跟现有 66-pattern +CATALOG 是一对一覆盖(每个 skill 对应 1 个或几个 P-XX),看起来很整齐,但底层有 6 个错判 +没真正读代码就标了 — 意味着对 Codex 怎么管 session/thread/memory 这块没真正搞懂。 + +`v0.6.2` 不解决覆盖率问题,只**诚实记录**。系统性补完由 v0.7.0 起按周推进。 + +## v0.6.1 changelog (previous) + +### Changed + +**Trigger descriptions rewritten across all 18 Skills** for better LLM matching. Each +`description:` frontmatter field now uses a structured 4-line format: + +```yaml +description: | + . + USE WHEN: . + TRIGGER PHRASES: . + SKIP WHEN: . +``` + +This makes the description **keyword-greppable** (so the LLM can match on real signals +like "ECONNREFUSED", "permission denied", "retries exceeded", "上下文满了" / "出错了" / +"重试") instead of trying to interpret abstract prose. + +All 18 Skills have their trigger phrases now spelled out in both English and Chinese, so +the LLM can match user language directly. Skill versions bumped to `0.1.1` (or +`1.0.1` for the v1.0 skills). + +## Try it + +Install from `/plugins` → **Local**, then ask any of: + +```text +"Read docs/internal-spec.md and summarize the data model — keep the full file off the main context" + +"Refactor the auth subsystem across these 5 files. Plan first, then execute." + +"Investigate why the test suite is flaky. Decompose into independent probes and run them in parallel." + +"I'm at turn 35 of an open-source contribution. Compress the conversation so I can keep going." + +"You just finished the migration — review your own diff for off-by-ones and edge cases." + +"Spawn a sub-agent to scan the codebase for unused imports. Give it a tight brief, not the full history." + +"Start a long dev server in the background so I can keep asking you things while it warms up." + +"Set the goal of this thread: migrate the auth subsystem to OIDC alongside SAML. Drift-check before +each non-trivial change." + +"This sub-task is a one-shot file reformat — use the cheap model for it." + +"Before you say 'done' on the auth refactor, run a completion audit. Show me the evidence for each requirement." + +"I'm about to spawn 4 sub-agents. Decide the fork_turns for each — full history or just the brief?" + +"Show me the sub-agent family tree — which are still running?" + +"This goal has a 20,000-token budget. Tell me at 50% / 80% / 100%." + +"The bash command just failed with 'permission denied'. Retry? Switch tool? Ask me?" + +"Read this 50K-line build log without filling the context. Stream-read it and summarize." + +"It's the end of the day. Write a handoff file so tomorrow's session can pick up." +``` + +**Expected result**: the agent picks the right Skill, follows the documented process, and produces +output that matches the Skill's output contract (see each Skill's `SKILL.md` for its specific +contract and example). + +## What this Plugin adds (v0.6.0, 18 Skills) + +Eighteen Skills, all Skill-only (no MCP server, no network access): + +| # | Skill | When to activate | v | +|---|---|---|---| +| 1 | `tool-output-budget` | A tool returns output you suspect is too large to keep verbatim (large logs, JSON, fetched HTML, minified files). | v0.1.0 → 0.1.1 | +| 2 | `context-pressure-compact` | The task is multi-step and long; the running `todowrite` exceeds 5 items, or the agent has been reasoning for many turns. | v0.1.0 → v1.0.1 | +| 3 | `parallel-fanout` | The user task is clearly decomposable into 2+ independent sub-tasks (independent files, independent probes, independent analyses). | v0.1.0 → v1.1.0 → v1.2.0 | +| 4 | `plan-stream-emit` | The user task is non-trivial and the user has not yet approved a plan; emit a structured plan before touching files. | v0.1.0 → 0.1.1 | +| 5 | `review-mode` | A non-trivial sub-task has just finished and the work is about to be marked done; the user wants verification before relying on the result. | v0.2.0 → 0.2.1 | +| 6 | `delegate-with-context` | About to call `task` to hand off a sub-task; the full conversation history is too large to forward and a minimal-context brief would do. | v0.2.0 → v1.1.0 → v1.2.0 | +| 7 | `world-state-tracking` | The task is long enough that the agent has lost the thread at least once, or `context-pressure-compact` is about to be applied. | v0.2.0 → 0.2.1 | +| 8 | `background-task` | A command is expected to take > 30 seconds, or the user wants a long-running process to coexist with ongoing work. | v0.2.0 → v0.1.2 → v0.2.0 | +| 9 | `goal-persistence` | A non-trivial task has just been stated (set the goal); the user has redirected (update the goal); or a `context-pressure-compact` is about to be applied (alignment check). | v0.3.0 → v1.0.1 | +| 10 | `model-router` | About to call `task` for a non-trivial sub-task, or about to spend the main model on work a cheaper model could do. | v0.3.0 → v0.3.3 → v0.4.0 | +| 11 | `completion-audit` | About to say "done" / "complete" / "ship it" on a non-trivial task. Derives requirements, identifies authoritative evidence, verifies each. | v0.4.0 → 0.4.1 | +| 12 | `fork-context-decision` | About to call `task` to hand off a sub-task. Decides how much parent context to give the sub-agent by inlining it into the `prompt`. | v0.4.1 → v0.1.0 → v0.2.0 → v0.3.0 | +| 13 | `subagent-family-tracking` | Spawned a sub-agent (or have one running). Track the parent/child tree so you do not lose children, duplicate work, or leave anyone running. | v0.5.0 → 0.5.1 | +| 14 | `goal-token-budgeting` | The user set an explicit `token_budget` on a goal. Track running usage against the budget and report the final number on completion. | v0.5.0 → 0.5.1 | +| 15 | `error-recovery-strategy` | A tool call, sub-agent task, or external operation failed. Decide between retry / switch / fallback / ask-user / skip. | v0.6.0 → 0.6.1 | +| 16 | `retry-with-backoff` | About to retry a `transient` error. State the policy first: max attempts, base delay, max delay, jitter, total time budget. | v0.6.0 → 0.6.1 | +| 17 | `streaming-output-reader` | A tool returns a long stream (SSE / WebSocket / `tail -f` / large log). Read in bounded chunks, synthesize, never loop. | v0.6.0 → 0.6.1 | +| 18 | `session-handoff` | The session is ending (user stepping away, time up, about to compact). Write a handoff file so next session can pick up in 30 seconds. | v0.6.0 → 0.6.1 | + +## Disclosure (per mcode plugin convention) + +The four sections below are explicit, independent disclosures as required by the mcode +plugin submission convention. They are the single source of truth for this Plugin's +runtime surface area; if any of them is false for a future change, update them in the +same commit. + +### No credentials + +The Plugin does not read, store, transmit, or request any credential. It does not declare +an OAuth flow, does not require environment variables, does not embed tokens, and does not +have a service account. The 23 Skills are pure Markdown instructions; activating a Skill +does not require or produce any secret material. + +### No network + +The Plugin makes no outbound network call. It does not bundle a fetch / download / +auto-update step; it does not register a webhook or a long-poll; it does not open a socket +of any kind. Skill contents are read from the local `skills/` directory only, and the +agent's existing tool surface (`bash`, `read`, `write`, `edit`, `grep`, `glob`, `task`) +is the only thing the Skills can ask the agent to do. + +### No telemetry + +The Plugin does not emit events, metrics, traces, or logs of its own. It does not register +a counter, does not tag rollouts, and does not write a heartbeat. Any observability the +Plugin produces is the same observability the agent would produce if a human typed the +same instructions by hand. + +### No third-party services + +The Plugin does not depend on any external service. It does not bundle a native binary, +does not call an MCP server, does not `npm install` anything at install time, and does +not require Python, Node, or any runtime besides the host agent. The 23 Skills are +self-contained Markdown; the `plugin.json` declares no `mcp.json` and no +`package.json`. + +## Requirements + +- **MiniMax Code** with Agent Plugins 1.0 support. +- **No Python, no Node, no external services.** These Skills are pure Markdown instructions; the + agent applies them with its existing tools (`bash`, `read`, `write`, `edit`, `grep`, `glob`, `task`). +- **No MCP server, no network, no credentials.** This Plugin does not start any process or open any + socket. It only adds Skill files to the agent. + +## Data and network + +- **No network access.** This Plugin adds Skills only; it does not call out. +- **No credentials, tokens, env vars, or telemetry.** The agent does not need any of these to + apply the Skills. +- **No data leaves your machine.** The Skills operate on whatever the agent can already see in + the workspace. + +## Security model + +The Skills are read-only instructions. They cannot be used to exfiltrate data, run untrusted code, +or escalate privileges beyond the agent's existing capability set. The only side effect is the +agent choosing to use its existing tools (e.g. `write` a compact summary to disk) — exactly as +the user would do manually. + +## How the Plugin is validated + +The Plugin was developed against the official `npm run check` workflow (see +`docs/plugin-compatibility.md` in the upstream `MiniMax-Code-Plugins` repo). It declares only +the portable subset (Skills + manifest), includes a real example prompt in this README, and +carries an Apache-2.0 LICENSE matching the host repository. + +## License + +Apache-2.0 diff --git a/plugins/antianqi/codex-harness-patterns/plugin.json b/plugins/antianqi/codex-harness-patterns/plugin.json new file mode 100644 index 0000000..f9a52dd --- /dev/null +++ b/plugins/antianqi/codex-harness-patterns/plugin.json @@ -0,0 +1,41 @@ +{ + "$schema": "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json", + "name": "codex-harness-patterns", + "version": "1.0.4", + "description": "Long-running task patterns distilled from OpenAI Codex harness v0.149.0 — tool output budgeting, context pressure compaction, parallel sub-agent fan-out, structured plan streaming, self-review, sub-agent delegation, world-state tracking, background task management, thread-level goal persistence, per-sub-task model routing, completion auditing, fork-context decision, sub-agent family tracking, goal token budgeting, error recovery strategy, retry with backoff, streaming output reader, session handoff, long-term memory, skill auto-selection, plugin authoring helper, tool discovery pattern, and session branch/fork. 23 Skills total covering the complete agent lifecycle: planning → decomposition → sub-agent parallelism → execution → state tracking → tool discovery → skill/plugin authoring → memory persistence → session branching. Activates when an agent must manage token budget, decompose work, sustain multi-step tasks without losing focus, coordinate sub-agents, pick the right model for the job, prove that a non-trivial task is actually done, stay within an explicit goal token budget, recover from transient failures, read streaming output without filling context, hand off a session cleanly, persist memory across sessions, write a discoverable skill, design a discoverable tool, author a marketplace plugin, or branch / fork / revert a session.", + "author": { + "name": "antianqi", + "url": "https://github.com/antianqi" + }, + "homepage": "https://github.com/antianqi/MiniMax-Code-Plugins/tree/main/plugins/antianqi/codex-harness-patterns", + "repository": "https://github.com/antianqi/MiniMax-Code-Plugins", + "license": "Apache-2.0", + "keywords": [ + "minimax-code", + "plugin", + "codex-harness", + "token-budget", + "context-compaction", + "sub-agent", + "task-planning", + "long-running", + "review", + "world-state", + "background-task", + "goal", + "model-routing", + "completion-audit", + "fork-context", + "family-tracking", + "token-accounting", + "error-recovery", + "retry", + "streaming", + "session-handoff", + "long-term-memory", + "skill-auto-select", + "plugin-author", + "tool-discovery", + "session-fork" + ] +} diff --git a/plugins/antianqi/codex-harness-patterns/skills/background-task/SKILL.md b/plugins/antianqi/codex-harness-patterns/skills/background-task/SKILL.md new file mode 100644 index 0000000..76bc1b6 --- /dev/null +++ b/plugins/antianqi/codex-harness-patterns/skills/background-task/SKILL.md @@ -0,0 +1,253 @@ +--- +name: background-task +description: | + Decide when to launch a long-running task in the background and how to refer to it later. Covers both the `task` tool (sub-agent background via `run_in_background: true`) and the `bash` tool (shell background via `run_in_background: true`). + USE WHEN: a sub-agent is expected to take > 1 minute, a shell command is expected to take > 30 seconds, the user wants a long-running process to coexist with ongoing work, you are about to block the conversation for an unbounded time, user said "background it" / "后台" / "don't block" / "non-blocking" / "run in background". + TRIGGER PHRASES: "background", "background it", "后台", "don't block", "non-blocking", "in the background", "run async", "long-running", "put it in the background". + SKIP WHEN: the sub-agent / command finishes in <5 seconds, the user explicitly wants to wait for output, the command is interactive (REPL, vim, ssh). +license: Apache-2.0 +compatibility: Targets MiniMax Code 0.2.4. Verified against the bundled `cli.js` schema. `task(...)` accepts `run_in_background: true` and returns a `task_id` for later `task_query` / `task_output` / `task_stop`. `bash(...)` accepts `run_in_background: true` and returns a job handle. The Codex-harness `bash(task_name=..., run_in_background=true, action="kill")` shape is **not** the mcode surface — mcode's `bash` has no `task_name` or `action` field; killing is via `task_stop(task_id=...)` for sub-agents and via the host's job-control API for shell jobs. +metadata: + author: antianqi + version: "0.2.0" + inspired-by: https://github.com/openai/codex/blob/main/codex-rs/core/src/unified_exec/ and protocol::Op::CleanBackgroundTerminals (design principle only; the mcode 0.2.4 surface is `task(run_in_background=true)` + `task_query` / `task_output` / `task_stop` for sub-agents, and `bash(run_in_background=true)` for shell jobs) + changes-from-v0.1.2: "Replaced the v0.1.2 'Codex-harness pseudocode + adapt the call' block with the actual mcode 0.2.4 surface. mcode `task` and `bash` both accept `run_in_background: true`; for sub-agents the returned handle is a `task_id` queried with `task_query` / `task_output` / `task_stop`. The Codex-harness `bash(task_name=..., action=\"kill\")` shape is removed because mcode's `bash` has neither `task_name` nor an `action` sub-action field (the validator in `cli.js:xza` only allows `command` / `timeout` / `run_in_background`). Killing a sub-agent uses `task_stop(task_id=...)`; killing a shell background job uses the host's own job-control API (the example now shows `Stop-Process -Id` on Windows and `kill -PID` on POSIX, both invoked through a foreground `bash` call rather than a fake `action=\"kill\"` field)." +--- + +# Background Task + +When a task (a sub-agent invocation or a shell command) is expected to take +more than ~30 seconds, the agent has two choices: + +1. **Block**: wait for the task to finish, holding the conversation hostage. +2. **Background**: launch it, get a handle, continue working, and check on it later. + +This Skill is about **knowing when to choose (2)** and **how to record the +handle** so the agent (or the user) can check on it later. + +## mcode 0.2.4 surface + +There are two background-capable tools on mcode 0.2.4, and the right one depends +on whether the background work is a sub-agent or a shell command. + +### Sub-agent background: `task(run_in_background: true)` + `task_query` / `task_output` / `task_stop` + +The canonical `task` schema: + +```text +task( + description: string, // 3-5 word label, required + prompt: string, // the brief, required + agent_name: "explore" | "worker" | "verifier", // required + run_in_background?: boolean // optional; true = async, false = sync (default) +) +``` + +When `run_in_background: true`, mcode returns immediately with a `task_id`. +The companion tools (also canonical in `cli.js`): + +| Tool | Purpose | Required fields | +|---|---|---| +| `task_query(task_id?, status?)` | List session tasks (omit `task_id`) or get one. | `task_id` for single fetch; `status` filter optional. | +| `task_output(task_id, offset?)` | Read a task's output incrementally. | `task_id` (required); `offset` (optional, byte offset for long output). | +| `task_stop(task_id, reason?)` | Request a background task to stop. | `task_id` (required); `reason` (optional, human-readable). | + +`run_in_background: false` (the default) blocks the calling turn until the +sub-agent finishes and returns its final result. + +### Shell background: `bash(run_in_background: true)` + +The `bash` tool's canonical schema (from `cli.js:xza`): + +```text +bash( + command: string, // required + timeout?: number, // optional, seconds + run_in_background?: boolean // optional; true = async, false = sync (default) +) +``` + +When `run_in_background: true`, the `bash` call returns immediately with a +job handle that the host's job-control API can target (Windows: +`Stop-Process -Id `; POSIX: `kill `, both invoked through a +foreground `bash` call rather than any `action="kill"` field). The exact +shape of the returned handle is not part of the public mcode 0.2.4 runtime +contract; the host's job-control API is the source of truth for the +underlying process id. **There is no `task_name=` and no `action="kill"` +field.** The Codex-harness shape `bash(task_name=..., run_in_background=true, +action="kill")` is **not** the mcode surface — mcode's `bash` validator +rejects any key outside `command` / `timeout` / `run_in_background`. + +Killing a shell background job: invoke the host's job-control API in a +**foreground** `bash` call. Windows: `Stop-Process -Id `. POSIX: +`kill `. The Skills do not pretend `bash(action="kill")` exists on +mcode 0.2.4. + +## When to use + +Activate when **any** of these is true: + +- A sub-agent is expected to take > 1 minute (deep research, multi-file + refactor, anything you cannot predict the duration of). +- A shell command is expected to take > 30 seconds (`cargo test`, `npm install`, + `docker build`, a long-running dev server, a large data download). +- The user explicitly says "background" / "后台" / "non-blocking" / "in the background". +- You need a long-running process to coexist with ongoing work (a dev server, a + watch script, a streaming pipeline). +- You would otherwise block the conversation on a result the user can come back + to later. + +## When NOT to use + +- The task / command finishes in <5 seconds. +- The user explicitly wants the output now (interactive REPL, vim, ssh, a build + whose output the next step depends on). +- The command is interactive (it expects a TTY or human input). + +## Process + +1. **Estimate the duration**. If unsure, assume the worst case. The mcode + `task` tool description (`run_in_background`) says "Set to true when the + sub-task is open-ended or expected to take more than ~1 minute (deep + research, multi-step investigation, large refactors, anything you cannot + predict the duration of), so you can keep working and the result is + reported back automatically when it completes. Leave false (the default) + for short, well-scoped sub-tasks whose result you need right now to + continue. When in doubt for a long or uncertain task, prefer true." +2. **Choose a descriptive handle**. The agent (and the user) will need to + recognise it later. `dev-server` is good. `task1` is bad. +3. **Launch in the background using the matching tool**: + - Sub-agent: `task(..., run_in_background: true)`; mcode returns a + `task_id`. Store it. + - Shell: `bash(command: "npm run dev", run_in_background: true)`; mcode + returns a job handle (the exact shape is not part of the public + runtime contract; the host's job-control API is the source of + truth). Store the handle. +4. **Record the handle**. In a multi-step task, store the handle (task_id, + job id, log path) somewhere persistent — in a `world-state-tracking` file, + a `session-handoff` note, or in the running brief. +5. **Continue working**. The conversation does not block on the background + task. +6. **When the result matters**: + - Sub-agent: `task_query(task_id)` for status, `task_output(task_id)` for + output, `task_stop(task_id)` to stop. + - Shell: foreground `bash` call against the host's job-control API + (`Get-Process -Id ` / `Stop-Process -Id ` on Windows; + `ps -p ` / `kill ` on POSIX). Read the log file or stdout + from the original launch. + +## Output contract + +After activating this Skill, the agent's next message MUST include: + +- The chosen **handle** (`task_id` or job id) and the **tool** that produced it + (`task` / `bash`). +- The **expected duration estimate**. +- The **log or status path** so a later turn can check on it. +- Whether the agent is **continuing** or **blocking** on the result. + +## Common pitfalls + +- **Launching and forgetting the handle** — the user comes back in an hour, + the agent has no idea which process was which. Always record the handle. +- **Re-using a generic name** — `task1` collides; `cargo-test` does not. +- **Polling too eagerly** — a 5-minute build polled every 5 seconds wastes + context. Poll on a sensible cadence (every minute for builds, every 5 minutes + for downloads). +- **Killing without saving output** — read the log first, then kill, otherwise + the result is lost. +- **Using Codex-only `bash(task_name=..., action="kill")` syntax** — mcode + 0.2.4's `bash` does not have those fields. Use `task_stop(task_id=...)` for + sub-agents and a foreground `bash` call to the host's job-control API for + shell jobs. +- **Passing `model_config_id` in a background `task()` call** — the `task` + tool does not accept it (see `model-router`). The model is the session's + current model. + +## Example + +The example below is **MiniMax Code 0.2.4 `task` / `bash` tool syntax**. Two +background launches are demonstrated. + +### Sub-agent background + +```text +# Launch a long-running research sub-agent in the background. +# mcode returns a task_id we can later query / read / stop. + +> task( + description="Research migration paths", + agent_name="explore", + run_in_background=true, + prompt=""" + Investigate migration paths from to in the + codebase. Produce a markdown report at + /notes/migration.md comparing the top 3 candidates + with code samples, risk notes, and a recommended path. + This may take 10+ minutes; you can take your time. + """ + ) +# Returns immediately with: +# { task_id: "tsk_01HXYZ...", status: "queued" } + +# Later, check status: +> task_query(task_id="tsk_01HXYZ...") +# { task_id: "tsk_01HXYZ...", status: "running", ... } + +# Read partial output (the report grows as the sub-agent works): +> task_output(task_id="tsk_01HXYZ...", offset=0) +# +# next_offset: 12345 + +# Stop it if the user changed their mind: +> task_stop(task_id="tsk_01HXYZ...", reason="user changed scope") +# { task_id: "tsk_01HXYZ...", status: "stopping" } +``` + +### Shell background + +```text +# Launch a long-running dev server in the background. +# mcode returns a job id we can later target via the host's job-control API. + +> bash( + command="npm run dev", + run_in_background=true + ) +# Returns immediately with a job handle. The exact shape is not +# part of the public mcode 0.2.4 runtime contract; the host's +# job-control API is the source of truth. Treat the handle as +# opaque and pass it to the host's job-control API in a +# foreground `bash` call (e.g. `Stop-Process -Id ` / +# `kill ` on POSIX) when you need to stop the job. + +# Later, check whether it is still alive (foreground bash call): +> bash( + command="Get-Process -Id 12345 | Select-Object Id,ProcessName,StartTime" + ) +# (or on POSIX: `ps -p 12345 -o pid,etime,cmd`) + +# Stop it when done (foreground bash call to the host's job-control API): +> bash( + command="Stop-Process -Id 12345" + ) +# (or on POSIX: `kill 12345`) +``` + +The **decision** (background, with a recorded handle) is the same; the +**execution mechanism** depends on whether the background work is a sub-agent +(use `task` + `task_query` / `task_output` / `task_stop`) or a shell command +(use `bash(run_in_background: true)` + the host's job-control API). + +## Verification checklist + +- [ ] Did you estimate the duration before choosing background vs blocking? +- [ ] Did you choose a **descriptive** handle (not `task1`)? +- [ ] Did you use the right tool — `task` for sub-agents, `bash` for shell + commands? +- [ ] Did you set `run_in_background: true` (not Codex's `bash(task_name=...)`)? +- [ ] Did you record the handle (task_id / job id / log path) in a persistent place? +- [ ] Did you tell the user "I launched X in the background, here's the handle and + log path"? +- [ ] If you stopped it, did you use `task_stop(task_id=...)` (sub-agent) or + `Stop-Process` / `kill` via a foreground `bash` call (shell)? diff --git a/plugins/antianqi/codex-harness-patterns/skills/completion-audit/SKILL.md b/plugins/antianqi/codex-harness-patterns/skills/completion-audit/SKILL.md new file mode 100644 index 0000000..2ded362 --- /dev/null +++ b/plugins/antianqi/codex-harness-patterns/skills/completion-audit/SKILL.md @@ -0,0 +1,158 @@ +--- +name: completion-audit +description: | + Before saying "done", derive requirements, find authoritative evidence, verify each is ✅. + USE WHEN: about to say "done" / "complete" / "ship it" / "I finished" / "做完了" on non-trivial task, about to mark `todowrite` step done, about to update goal to `complete`, user has been waiting for "done" for several turns, "looks good" / "should be fine" / "应该好了" / "我试过没报错" / "我跑了测试都过了" / "I tested it" / "trust me" / "should work". + TRIGGER PHRASES: "做完了", "done", "complete", "ship it", "好了", "完成", "搞定", "I think we're done", "应该好了", "looks good", "我试过没报错", "我跑了测试都过了", "I tested it", "trust me", "should work". + SKIP WHEN: one-line edit, user can see result in chat immediately, user explicitly said "ship it" / "no more review" in this turn. +license: Apache-2.0 +compatibility: Requires MiniMax Code with Agent Plugins 1.0 support. +metadata: + author: antianqi + version: "0.1.1" + inspired-by: https://github.com/openai/codex/blob/main/codex-rs/ext/goal/templates/goals/continuation.md +--- + +# Completion Audit + +Before you say "done," prove it. Not from memory, not from intent, not from "the tests passed" — +from **authoritative evidence for each requirement, against the actual current state**. + +This Skill is the difference between "I think I'm done" and "I have proven I am done." It is the +single most expensive lesson in agent work: **agents are confident they are done far more often +than they actually are**, and the cost of that false confidence is paid by the user. + +## When to use + +Activate when **any** of these is true: + +- You are about to say "done" / "complete" / "ship it" / "I finished" on a non-trivial task. +- A `todowrite` step is about to be marked `[x]` and that step touches anything the user will + rely on. +- An active thread goal (`goal-persistence` Skill) is about to be marked `complete`. +- The user has been waiting for "done" for several turns and you feel the urge to say it just to + end the turn. +- The work spanned multiple tool calls, multiple files, or multiple sub-tasks. + +## When NOT to use + +- A one-line edit. The cost of the audit is higher than the cost of a wrong "done". +- The deliverable is observable by the user in this turn (e.g. you just printed the result to + the chat). They can see it; you don't need to audit yourself. +- The user explicitly said "ship it" or "no more review" in this turn. + +## Process + +1. **Stop. Do not say "done" yet.** Even if it feels done, run this Skill. +2. **Derive concrete requirements** from the original objective — verbatim where possible. If + the user said "fix the auth bug," the requirements are "auth works for existing users" and + "no new bug introduced," not "I edited the auth file." Translate surface requests into + outcome requirements. +3. **For each requirement, identify the authoritative evidence** that would prove it. Not + "I think it works" — what would *you* check, given infinite time? + + | Requirement | Authoritative evidence | + |---|---| + | "auth works for existing users" | existing test suite green + 1 manual smoke | + | "doc updated" | grep / file content showing the new line | + | "API returns 200 on happy path" | curl output | + | "user can log in" | screenshot / log of one full login round-trip | + +4. **Inspect that evidence** — the *current* state, not a memory. The test you ran three + turns ago is no longer authoritative if the code has changed since. Re-run, re-read, + re-grep. +5. **For each item, classify the result**: + + - ✅ **proves completion** — the evidence matches the requirement unambiguously + - ❌ **contradicts completion** — the evidence shows the requirement is not met + - 🟡 **incomplete work** — partial evidence, some sub-requirement unmet + - ⚪ **too weak to verify** — the evidence does not cover the requirement's scope + - 🚫 **missing** — no evidence was gathered + +6. **If any item is not ✅**, do not declare done. State the unfinished item and continue. +7. **If all items are ✅**, declare done in this shape: + + ```text + ✅ Done. Verified against: + - : — ✅ + - : — ✅ + - ... + ``` + +8. **On active thread goal**: report the final token usage to the user + (per `goal-persistence` Skill — token accountability). State the actual final number, not a + rough estimate. + +## Output contract + +The user sees, in this order: + +- One-line "verifying before declaring done" preamble. +- The audit table (one row per requirement, one column per: requirement, evidence, result). +- The verdict: ✅ all green / 🟡 unfinished item(s) / ❌ contradiction(s). +- (If all green + active goal) The final token usage. + +## Example + +```text +Verifying before declaring "auth refactor done". + +| Requirement | Evidence | Result | +|--------------------------------------------|---------------------------------------------------|--------| +| 12 existing SAML tests still pass | `cargo test auth::saml` (just re-ran) | ✅ | +| New OIDC login works with Okta | `cargo test auth::oidc` + manual smoke (logged in) | ✅ | +| `auth.oidc_enabled` defaults to `false` | `grep "oidc_enabled" config/default.toml` | ✅ | +| No new dependencies in `Cargo.toml` | `git diff Cargo.toml` | ✅ | +| Docs updated in `docs/auth.md` | `grep "oidc_enabled" docs/auth.md` | ✅ | +| No off-by-one in `expires_at` | manual code review of `src/auth/oidc/mod.rs:42` | ✅ | + +Token usage: 18,420 / 20,000 (92% of goal budget). +✅ Done. +``` + +Counter-example (audit caught a hole): + +```text +Verifying before declaring done. + +| Requirement | Evidence | Result | +|--------------------------------------|---------------------------------------------------|--------| +| Existing tests pass | `cargo test` | ✅ | +| Manual smoke test passes | (not run) | ⚪ | +| Public API unchanged | `git diff src/auth/mod.rs` | ❌ `login()` signature changed — added `provider` param | +| Docs mention new OIDC config | `grep "oidc" docs/auth.md` | ❌ doc only describes SAML | + +🟡 Two requirements unmet. The public API changed but was not declared, and the +docs only describe SAML. The user must decide: revert the public API change, or +accept it and update the docs + declare the API break. +``` + +## Common pitfalls + +- **Do not skip the audit because "I just ran the tests."** The tests you ran three + turns ago are not the current state. Re-run, re-read. +- **Do not classify "I wrote the code" as evidence.** Writing code is not the requirement; + the requirement is what the code *does*. Substitute "the code does X" with "I verified X + by running Y." +- **Do not let the audit become a rubber stamp.** If the verdict is "all green" 100% of + the time, the audit is not working. The whole point is to catch what you missed. +- **Do not declare done with 🟡 items.** The verdict must be all ✅ or you do not + declare done. Surface the unfinished items. +- **Do not treat "user said ship it" as a reason to skip the audit.** The user said ship + it because they *expect* the audit to have been done. Skipping is a betrayal. +- **Do not substitute a narrower, safer, or merely-compatible solution.** If the user + asked for OIDC and you built OAuth2 "because it's similar," the requirement is unmet even + if the tests pass. +- **Do not mark a goal complete because the budget is nearly exhausted or because you are + stopping work.** The audit is the only thing that gets to declare done. + +## Verification checklist + +- [ ] Did you stop before saying "done"? +- [ ] Did you derive concrete requirements (not surface requests)? +- [ ] For each requirement, did you identify the *authoritative* evidence? +- [ ] Did you inspect the *current* state (not memory of past work)? +- [ ] Is each item classified as ✅ / ❌ / 🟡 / ⚪ / 🚫? +- [ ] Is the verdict all ✅? If not, did you surface the unfinished items? +- [ ] (Active goal) Did you report final token usage? +- [ ] Did the user see the audit table before you declared done? diff --git a/plugins/antianqi/codex-harness-patterns/skills/context-pressure-compact/SKILL.md b/plugins/antianqi/codex-harness-patterns/skills/context-pressure-compact/SKILL.md new file mode 100644 index 0000000..d8653c3 --- /dev/null +++ b/plugins/antianqi/codex-harness-patterns/skills/context-pressure-compact/SKILL.md @@ -0,0 +1,176 @@ +--- +name: context-pressure-compact +description: | + Compress a long-running multi-step task into a structured snapshot before continuing. + USE WHEN: `todowrite` > 5 items, after ~20 tool calls, context getting full, agent has lost track of goal, user said "compact" / "summarize" / "refocus" / "压缩" / "总结" / "到哪了", before context window fills (>80%), before `context-pressure-compact` boundary. + TRIGGER PHRASES: "compact", "summarize", "refocus", "压缩", "总结", "到哪了", "context 满了", "忘了目标", "we're getting lost", "compress", "snapshot". + SKIP WHEN: short task (<5 tool calls), user in middle of dictating a request, user said "do not summarize" / "keep everything". +license: Apache-2.0 +compatibility: Requires MiniMax Code with Agent Plugins 1.0 support. +metadata: + author: antianqi + version: "1.0.1" + inspired-by: https://github.com/openai/codex/blob/main/codex-rs/core/src/compact.rs and core/src/compact_remote_v2.rs + changes-from-v0.1.0: "Added the 64K retention budget concept (P-10 v2); added 'discarded N tool calls and M lines' reporting rule; cross-referenced world-state-tracking and goal-persistence so compaction is the single coordination point." +--- + +# Context Pressure Compact + +Compress the running state of a long task into a structured snapshot, then keep working +from the snapshot. The agent loses the noisy middle (failed attempts, finished steps, stale +tool output) but keeps the goal, the decisions, and the next move. + +**v1.0 update**: now incorporates the 64K retention budget from Codex's `compact_remote_v2.rs` +— the "important" messages preserved after a compact should target ~64,000 tokens, not be +unbounded. + +## When to use + +Activate when **any** of these is true: + +- The active `todowrite` has more than 5 items, **and** at least 2 are still in progress. +- The agent has executed roughly 20 or more tool calls since the user request. +- The user says "compact", "summarize so far", "refocus", "we're getting lost", or + "let me see the state". +- A tool call from `tool-output-budget` is being applied to a string the agent will not need + again (it has been superseded by newer output). +- A sub-task boundary (a self-contained feature is done; the next user step starts a new one). +- The estimated total tokens (history + current message) is approaching the model context + window (typically 80% of the window is the trigger). + +Do **not** use this Skill for short tasks (< 5 tool calls, < 3 `todowrite` items). Compaction has +its own cost; the savings only matter once the conversation is genuinely heavy. + +## When NOT to use + +- A single one-shot question. The user wants an answer, not a checkpoint. +- The user is in the middle of dictating a multi-step request. Finish listening first. +- The user explicitly said "do not summarize" or "keep everything". + +## Process + +1. **Freeze new work.** Do not start any new tool call before the snapshot is written. +2. **Target the 64K retention budget.** After compact, the **retained** messages (the + important ones — current goal, recent decisions, key file paths) should target ~64,000 + tokens. Discard the rest. This is `RETAINED_MESSAGE_TOKEN_BUDGET` from + `compact_remote_v2.rs`. If you retain more, the next compaction will arrive sooner than + expected; if you retain much less, the agent will lose context. + +3. **Write the snapshot** to a single fenced block, in this exact shape: + + ```markdown + ## Compact Snapshot — + + **Goal**: + + **Done** (these are finished; do not re-do them): + - + + **In progress** (these are partially done; carry the partial state forward): + - + + **Decisions made** (so future you doesn't re-argue them): + - + + **Key file paths** (absolute paths the next step will need): + - /path/to/foo + - /path/to/bar + + **Blockers / open questions** (so the user can answer them upfront next turn): + - + + **Next concrete step**: + + **Retained token estimate**: (target: ~64K, see RETAINED_MESSAGE_TOKEN_BUDGET) + **Discarded this turn**: + ``` + +4. **Optionally persist to disk** if the user has a working directory. Default path: + `.minimax/snapshots/-.md`. The user can `read` it later to reload + context. +5. **Drop the noisy middle from the next prompt.** After the snapshot, your next response + should start from "Next concrete step", not from re-stating the goal. +6. **Continue working** as if the snapshot is the only context. Do not re-fetch the files + you already listed under "Key file paths" unless you need to re-read them. +7. **Coordinate with other state files** (cross-references): + - **`goal-persistence`** file: survives compaction unchanged (its file is on disk). + - **`world-state-tracking`** file: survives compaction unchanged. + - **`subagent-family-tracking`** file: survives compaction unchanged. + - **`goal-token-budgeting`** usage log: keep the latest row, drop the history. + - **`todowrite`**: keep Done/In progress sections, drop the granular "attempted X then + Y then Z" history. + +## Output contract + +Every time you apply this Skill, the user sees: + +- The Compact Snapshot block (as above). +- An optional one-line "discarded N tool calls and M lines of intermediate output" note. +- The next concrete step, phrased as an action the user can sanity-check. +- The retained token estimate (so the user knows the budget is being respected). + +## Example + +```markdown +## Compact Snapshot — 2026-08-23T23:55:00Z — step 7 + +**Goal**: Refactor the auth subsystem to support OIDC without breaking the existing SAML path. + +**Done** (these are finished; do not re-do them): +- Mapped current auth flow in src/auth/. Wrote findings to .minimax/snapshots/auth-flow.md +- Identified 4 injection points: login(), callback(), refresh(), logout() +- Confirmed test coverage: 12 of 14 files have unit tests (2 missing: logout, session) + +**In progress** (these are partially done; carry the partial state forward): +- Designing the OIDC adapter interface. Stopped at: how to represent the "provider" enum + vs the existing "IdP" interface. Need to decide: 1) extend IdP, 2) new OidcProvider sibling, + 3) generic Provider with config-driven dispatch. + +**Decisions made** (so future you doesn't re-argue them): +- Keep SAML on the legacy code path; OIDC gets a parallel module. (Reason: SAML contract is + frozen, no test budget to re-validate.) +- Reject (3) generic Provider — too much config surface for marginal benefit. + +**Key file paths** (absolute paths the next step will need): +- /repo/src/auth/idp.rs +- /repo/src/auth/callback.rs +- /repo/tests/auth/ + +**Blockers / open questions** (so the user can answer them upfront next turn): +- Should the OIDC module own token storage, or reuse the existing session store? +- Does IT have a preferred OIDC library (openidconnect vs oauth2)? + +**Next concrete step**: Draft the OidcProvider trait + one impl for `provider = "okta"`, then +show the diff to the user before touching the callback. + +**Retained token estimate**: ~58,000 tokens (target: ~64K, well within budget) +**Discarded this turn**: 14 tool calls, ~12,000 lines of intermediate output +``` + +## Common pitfalls + +- **Do not rewrite history.** The snapshot records what actually happened, including the + wrong path you took. Future you needs the wrong path to avoid re-walking it. +- **Do not omit Blockers.** This is the most valuable section — it's how the user unblocks + you with one sentence instead of three round trips. +- **Do not skip "Key file paths".** Absolute paths save the next turn from `glob` and `grep`. +- **Do not snap every turn.** A snapshot after every tool call is noise. Use the triggers + above. +- **Do not nest snapshots.** One snapshot is the new ground truth; the previous one is + superseded and can be discarded (or moved to `.minimax/snapshots/archive/`). +- **Do not exceed the 64K retention target.** A snapshot of 200K tokens defeats the purpose. + If your retention is naturally > 64K, you need to compress *more* (drop more history, + shorten the in-progress description), not less. +- **Do not duplicate other state files.** The goal / world-state / family files survive + on disk. Reference them by path; do not copy them into the snapshot. + +## Verification checklist + +- [ ] Is the goal copied verbatim from the user? +- [ ] Are Done / In progress / Decisions / Paths / Blockers / Next step all present and + non-empty (or explicitly "none")? +- [ ] Is the retained token estimate present and within the 64K target (±20%)? +- [ ] Did you avoid starting a new tool call before the snapshot was written? +- [ ] Did you drop the noisy middle from the next prompt? +- [ ] Did the user get a chance to answer Blockers before you kept going? +- [ ] Did you reference (not duplicate) other state files by path? diff --git a/plugins/antianqi/codex-harness-patterns/skills/delegate-with-context/SKILL.md b/plugins/antianqi/codex-harness-patterns/skills/delegate-with-context/SKILL.md new file mode 100644 index 0000000..4a906a1 --- /dev/null +++ b/plugins/antianqi/codex-harness-patterns/skills/delegate-with-context/SKILL.md @@ -0,0 +1,152 @@ +--- +name: delegate-with-context +description: | + Hand off a sub-task to a sub-agent with a tight, complete brief in the `prompt` — not the full conversation history. Apply the 4-part message envelope (Task name / Sender / Task / Payload + return path). + USE WHEN: about to call `task()` to hand off a sub-task, the full conversation history is too large to forward, a minimal-context brief would do, the previous sub-agent failed because the brief was incomplete. + TRIGGER PHRASES: "delegate", "hand off", "sub-agent", "delegate this", "delegate to", "派给", "委派", "让 sub-agent 干", "把 ... 交给 ...". + SKIP WHEN: the sub-task is so trivial a `read` will do, you are about to do the work yourself, the user explicitly wants you (not a sub-agent) to do it. +license: Apache-2.0 +compatibility: Targets MiniMax Code 0.2.4 `task` tool. Verified against the bundled `cli.js` schema (`description` / `prompt` / `agent_name` / `run_in_background`). The 4-part envelope is host-neutral design; on mcode the envelope goes into the `prompt` string. `agent_name` is the canonical mcode spelling (`explore` / `worker` / `verifier`); `mavis` is the root agent, not a sub-agent. +metadata: + author: antianqi + version: "1.2.0" + inspired-by: https://github.com/openai/codex/blob/main/codex-rs/protocol/src/protocol.rs (InterAgentCommunication) and core/src/session/multi_agents.rs (CollabAgentSpawn); the 4-part envelope is the portable design; on mcode the envelope fills the `prompt` field + changes-from-v1.1.0: "Replaced `subagent_type=` with the canonical mcode `agent_name=`. Replaced `brief=` with `prompt=`. Dropped `mavis` from the sub-agent list (mavis is the root). The 4-part envelope is unchanged but now lives inside the `prompt` string, not in a separate `brief` parameter. The host-pseudocode 'Codex-harness style' block was removed; the mcode 0.2.4 schema is now the only one shown." +--- + +# Delegate with Context + +When handing off work to a sub-agent, the agent has two extremes: + +1. **Forward everything**: the sub-agent sees the full parent history. Costs + tokens, dilutes focus, may leak irrelevant detail. +2. **Forward nothing**: the sub-agent gets a one-line "go do X". The brief is + almost always incomplete, and the sub-agent re-derives incorrectly. + +This Skill is about the **middle ground**: a tight, complete, structured brief that +gives the sub-agent everything it needs and nothing it does not. + +## mcode 0.2.4 surface + +The `task` tool on mcode 0.2.4: + +```text +task( + description: string, // 3-5 word label, required + prompt: string, // the brief, required + agent_name: "explore" | "worker" | "verifier", // required + run_in_background?: boolean // optional +) +``` + +The 4-part envelope is **the design**; on mcode it goes into the `prompt` +string verbatim. `agent_name` is the canonical spelling. `agent_name=` is +accepted as a runtime alias but the Skills prefer the canonical form. + +`mavis` is the root agent (the calling session itself), not a sub-agent. It +has no `agent.md` manifest and cannot be used as `agent_name`. + +## When to use + +Activate when **any** of these is true: + +- You are about to call `task` to hand off a sub-task. +- The full conversation history is too large to forward (cost / focus). +- A previous sub-agent failed because the brief was incomplete. +- You want the sub-agent's work to be auditable against a written contract. + +## When NOT to use + +- The sub-task is so trivial a single `read` will do (no sub-agent needed). +- You are about to do the work yourself. +- The user explicitly wants you (not a sub-agent) to do it. + +## Process + +1. **Classify the sub-task** (see `fork-context-decision`): + - Self-contained: `none` (just the brief in `prompt`). + - Needs prior context: `N` or `all` → inline the prior turns into `prompt` before + the brief. +2. **Pick the sub-agent type** from `{explore, worker, verifier}` based on what + the sub-task needs (read / write+run / run-only). +3. **Write the 4-part envelope** below. The envelope is the **portable** part of + the brief — host `task` tools all accept a brief string. +4. **Choose context level** (see `fork-context-decision`) and inline the chosen + context into `prompt` before the envelope (or skip if `none`). +5. **Document the return path** — how the sub-agent should hand the result back. + +## The 4-part envelope + +Every sub-agent brief (the body of the `prompt` field) MUST have these 4 parts, +in order: + +```text +Task name: +Sender: +Task: +Payload: +Return: +``` + +Each part is mandatory. Skipping any one is the difference between a working +sub-task and a confused one. + +### Field-by-field + +| Field | Purpose | Bad | Good | +|---|---|---|---| +| Task name | The handle you'll refer to later. | `task1` | `investigate-lint-flake` | +| Sender | Who is asking, so the sub-agent knows the audience. | _(omitted)_ | `main agent` | +| Task | One-sentence scope. | `fix the tests` | `Investigate why test_lint.py flakes on Windows but not Linux. Produce a 1-paragraph root-cause analysis.` | +| Payload | The actual content the sub-agent needs. | `see above` | Links to the file, the prior turn's tool output, the user's exact request. | +| Return | Where the result goes, in what format. | _(omitted)_ | `Append a section to /notes/lint.md titled "## Windows flake root cause" with 1 paragraph.` | + +## Common pitfalls + +- **Omitting the return path** — the sub-agent finishes and has no idea what + to do with the result. Always specify. +- **Putting the brief in `Task` and the question in `Payload`** — the sub-agent + sees both, but the wrong field is the "one-sentence scope". Keep `Task` + short. +- **Forwarding the full history when `none` would do** — costs tokens and + dilutes focus. Decide first. +- **Using `agent_name="mavis"`** — mavis is the root agent, not a sub-agent. + Use `explore` / `worker` / `verifier`. +- **Writing the envelope in a separate `brief=` field** — mcode 0.2.4 does not + expose a `brief` field. Put it in `prompt`. + +## Example + +The example below is **MiniMax Code 0.2.4 `task` tool syntax**. The envelope +is the `prompt` body; the call shape is the only one that exists on mcode 0.2.4. + +```text +> task( + description="Investigate lint flake", + agent_name="worker", // or "explore" if read-only + prompt=""" + Task name: investigate-lint-flake + Sender: main agent + Task: Investigate why /tests/test_lint.py flakes on + Windows but not Linux. Produce a 1-paragraph root-cause + analysis. + Payload: /tests/test_lint.py (line 47 is the failure); + prior turn tool output (inlined above this prompt if + context level > none). + Return: Append a section to /notes/lint.md titled + "## Windows flake root cause" with 1 paragraph. + """ + ) +``` + +The **envelope** is the design; on mcode the envelope fills the `prompt` +field. There is no separate `brief` parameter. + +## Verification checklist + +- [ ] Did you classify the sub-task (self-contained vs context-dependent)? +- [ ] Did you pick `agent_name` from `{explore, worker, verifier}`? +- [ ] Did you write all 4 envelope parts (Task name / Sender / Task / Payload / Return)? +- [ ] Did you specify the **return path** (where the result goes)? +- [ ] Did you choose the right context level (via `fork-context-decision`)? +- [ ] Did you put the envelope inside the `prompt` field (not a separate `brief`)? diff --git a/plugins/antianqi/codex-harness-patterns/skills/error-recovery-strategy/SKILL.md b/plugins/antianqi/codex-harness-patterns/skills/error-recovery-strategy/SKILL.md new file mode 100644 index 0000000..1a77340 --- /dev/null +++ b/plugins/antianqi/codex-harness-patterns/skills/error-recovery-strategy/SKILL.md @@ -0,0 +1,154 @@ +--- +name: error-recovery-strategy +description: | + Classify error into 4 buckets (transient / deterministic / stale / unknown) and pick one of 5 actions (retry / switch / fallback / refresh-then-retry / ask-user / skip). + USE WHEN: tool returns non-success, sub-agent `status: closed-failed`, exception escapes, timeout fires, weird partial-success result, ECONNREFUSED / 5xx / 429 / timeout / permission denied / "command not found" / "fail" / "error" / "出错了" / "挂" / "失败". + TRIGGER PHRASES: "出错了", "failed", "挂", "error", "失败", "fail", "permission denied", "command not found", "ECONNREFUSED", "timeout", "挂了", "再试一次", "retry", "这不行", "没用", "fallback", "退路", "不行", "跑不通", "broken". + SKIP WHEN: operation succeeded, error is in user input (clarification case), error is part of expected flow (grep 0 matches). +license: Apache-2.0 +compatibility: Requires MiniMax Code with Agent Plugins 1.0 support. +metadata: + author: antianqi + version: "0.1.2" + inspired-by: https://github.com/openai/codex/blob/main/codex-rs/code-mode/src/grpc_session/reconnect.rs and core/src/session/multi_agents.rs + changes-from-v0.1.1: "Audit gap: the sub-agent invocation example in '## Example' (line 115) used the Codex-style `task(subagent=...)` shape; switched to the canonical mcode 0.2.4 `task(agent_name=..., prompt=...)` shape (mcode accepts `subagent_type=` as a runtime alias but `agent_name=` is the canonical form per `cli.js:B6c`). The round-1 72952c9 amend touched 4 Skills (fork-context-decision / delegate-with-context / parallel-fanout / model-router) and missed this 5th; the v1.0.4 round-2 close-out also missed it. Caught by the v1.0.4 audit sweep across all 23 Skills' code blocks. The rest of the Skill body is unchanged." +--- + +# Error Recovery Strategy + +When something fails, the default human reaction is "retry." That is often the **wrong** +default. Retrying a permission-denied file write burns the same error three times in a row. +Retrying a network timeout that won't resolve in 30 seconds burns three minutes. + +This Skill codifies the decision: **categorize the error first, then pick one of five +recovery actions, then commit to it explicitly.** + +## When to use + +Activate when **any** of these is true: + +- A tool call returns a non-success result (non-zero exit, HTTP 4xx/5xx, exception, + error message). +- A sub-agent reports `status: closed-failed` in the family file. +- An exception escapes from any of your own code or a library you called. +- A timeout fires on a long-running operation. +- A "weird" result comes back that might be a partial success (e.g. command exited 0 + but produced no output where you expected output). + +## When NOT to use + +- The operation succeeded. Do not second-guess success. +- The error is in user input (bad prompt, missing file the user should provide). That is + not a recovery case; it is a clarification case. +- The error is part of expected flow (e.g. a `grep` returning 0 matches is an exit-1, but + it is not a failure for the search use case). + +## Process + +1. **Stop. Do not retry yet.** Even if the obvious answer is "retry," run this Skill. +2. **Categorize the error** into one of four buckets: + + | Bucket | Signals | Examples | + |---|---|---| + | **transient** | Will probably succeed if tried again soon | Network timeout, HTTP 429/503, "ECONNRESET", "temporarily unavailable", rate limit | + | **deterministic** | Will fail every time the same way | Permission denied, file not found, "command not found", invalid syntax, wrong credentials | + | **stale** | Was true once, not anymore | "Index out of date", "stale lock", "file modified since read" | + | **unknown** | Cannot classify from the message alone | Generic exception, empty error, "something went wrong" | + +3. **Pick one of five recovery actions** based on the bucket: + + | Bucket | Default action | When to deviate | + |---|---|---| + | **transient** | `retry-with-backoff` (see next skill) | Never skip the backoff. If retries exceed the budget, escalate to `ask-user`. | + | **deterministic** | `switch` (try an alternative tool) or `fallback` (degrade gracefully) | If no alternative exists, `ask-user`. | + | **stale** | `refresh-then-retry` (re-read state, then retry once) | If still failing after one refresh, escalate to `ask-user`. | + | **unknown** | `ask-user` (or `skip-with-warning` if user signal allowed it) | Never silently retry an unknown error. | + +4. **Execute the chosen action explicitly** — do not bundle it with other tool calls. + The user must be able to see "I retried" / "I switched to X" / "I asked you" as a discrete + step in the response. + +5. **Record the decision in the response** so the user can audit: + + ```text + Recovery decision: + Reason: + ``` + +6. **After the action**, re-evaluate. If the action also failed, re-categorize (the bucket + may have changed — e.g. a "transient" error that persists for 5 retries is now a + "deterministic" error in practice) and pick a new action. + +## Output contract + +The user sees, in this order: + +- One-line error summary (what went wrong, with the tool / file / op named). +- Bucket classification: `transient` / `deterministic` / `stale` / `unknown`. +- Chosen action: `retry` / `switch` / `fallback` / `refresh-then-retry` / `ask-user` / `skip`. +- One-line reason citing the bucket. +- (If retry) the backoff plan: "N attempts, M seconds between, exponential with jitter." +- (If ask) the user-facing question with concrete options. +- (If fallback / skip) what degraded behaviour the user is now getting. + +## Example + +```text +> bash(command="cat /etc/hosts") + +Error: cat: /etc/hosts: Permission denied +Recovery decision: switch +Reason: bucket=deterministic (permission denied won't change on retry); + switching to `read` with explicit `path=/etc/hosts` (read may have different + permission semantics than shell cat) before asking the user. +``` + +```text +> bash(command="curl https://internal-api.example.com/health") + +Error: connect ECONNREFUSED 10.0.0.5:443 +Recovery decision: retry +Reason: bucket=transient (network refused); will retry 3 times with 2s/4s/8s backoff + and 500ms jitter; if all fail, escalate to ask-user. +``` + +```text +> task(agent_name="explore", prompt="...") + +Status: closed-failed. Sub-agent error: "context window exceeded". +Recovery decision: ask-user +Reason: bucket=unknown (sub-agent did not return a clear error class); do not silently + retry with a smaller brief; surface to user with options: + (a) reduce the sub-task scope + (b) switch to a model with larger context + (c) skip this sub-task +``` + +## Common pitfalls + +- **Do not default to retry.** Retry is only correct for `transient` (and a few `stale`). + For `deterministic` and `unknown`, retry is the most expensive wrong answer. +- **Do not bundle the recovery with other tool calls.** A retry hidden inside a larger + batch of work is invisible. Always surface the recovery as a discrete step. +- **Do not re-categorize silently.** If you categorize as `transient`, retry 3 times, + and it still fails, the bucket is now `deterministic` or `unknown` — say so out loud. +- **Do not ask the user a vague question.** "What should I do?" is not an option. Give + the user 2-4 concrete options based on the bucket. +- **Do not skip-with-warning without permission.** The user did not pre-authorize + silent skips. If the work is optional, the user should have said so at the start. +- **Do not blame the tool.** The tool did what it was told. Categorize the error + honestly, not defensively. +- **Do not loop on retry forever.** Always have a max-attempt budget; on exhaustion, + escalate to `ask-user`. + +## Verification checklist + +- [ ] Did you categorize the error into one of four buckets before picking an action? +- [ ] Did you pick one of five actions based on the bucket (not the default)? +- [ ] Did you state the recovery decision in the response, with the bucket and reason? +- [ ] (Retry) Did you specify the backoff plan (attempts, intervals, jitter)? +- [ ] (Ask) Did you give 2-4 concrete options, not "what should I do?" +- [ ] (Switch / Fallback) Did you name the alternative tool / the degraded behaviour? +- [ ] (Skip) Did you confirm the user pre-authorized this work as optional? +- [ ] Did you re-evaluate after the action and re-categorize if it failed? +- [ ] Is the recovery step a discrete line in the response (not bundled)? diff --git a/plugins/antianqi/codex-harness-patterns/skills/fork-context-decision/SKILL.md b/plugins/antianqi/codex-harness-patterns/skills/fork-context-decision/SKILL.md new file mode 100644 index 0000000..c6468ea --- /dev/null +++ b/plugins/antianqi/codex-harness-patterns/skills/fork-context-decision/SKILL.md @@ -0,0 +1,232 @@ +--- +name: fork-context-decision +description: | + Decide how much parent context to include in a sub-agent's `prompt` before spawning it. Pick "all / N turns / brief only" explicitly, not by accident. + USE WHEN: about to call `task()` to hand off work, designing a multi-agent flow, sub-agent failed and debugging whether cause was over- or under-forking, user said "give it the full history" / "no history" / "just the brief" / "don't carry context" / "深度 fork" / "不要带 context". + TRIGGER PHRASES: "fork 深度", "give it the full history", "深 fork", "no history", "just the brief", "不要带 context", "fork 0", "fork all", "完全独立会话", "轻量 context". + SKIP WHEN: sub-task is trivial (one-line read), you have already decided "no context" (no decision to make). +license: Apache-2.0 +compatibility: Targets MiniMax Code 0.2.4 `task` tool. Verified against the bundled `cli.js` schema (`description` / `prompt` / `agent_name` / `run_in_background`). `agent_name` is the canonical mcode spelling; `agent_name` is accepted as an alias by mcode's normaliser but the Skills prefer the canonical form. The mcode `task` tool has no `history` / `fork_turns` / `context_size` parameter — all context sharing is done by what you write into the `prompt` string itself. +metadata: + author: antianqi + version: "0.3.0" + inspired-by: https://github.com/openai/codex/blob/main/codex-rs/core/src/session/multi_agents.rs (design principle; the 3 fork modes are portable; the prompt-content decision is host-neutral) + changes-from-v0.2.0: "Removed the v0.2.0 `history=N` PLACEHOLDER — the mcode 0.2.4 `task` tool has no context-sharing parameter, so the 3 fork modes (all / N / none) are now expressed by what the calling agent writes into the `prompt` (full conversation dump / last N turns inline / brief only). Replaced `subagent_type=` with the canonical mcode `agent_name=`. Dropped `mavis` from the subagent list because `mavis` is the root agent (it has no `agent.md` subagent manifest and cannot be used as `agent_name`); the 3 actual mcode sub-agent types are `explore` / `worker` / `verifier`." +--- + +# Fork Context Decision + +How much parent context to pass to a sub-agent is the **single largest cost lever** in +multi-agent work. Too much and you double the model's context; too little and the +sub-agent cannot do its job because it cannot see what came before. Pick wrong either +way, the work slows down or silently fails. + +This Skill codifies the decision so the agent makes it explicitly, not by accident. + +## The hard fact about mcode 0.2.4 + +The mcode 0.2.4 `task` tool **has no parameter for context sharing**. The +canonical schema is: + +```text +task( + description: string, // 3-5 word label, required + prompt: string, // the task itself, required + agent_name: string, // "explore" | "worker" | "verifier", required + run_in_background?: boolean // optional +) +``` + +`agent_name` is accepted as a runtime alias (the normaliser at `cli.js:j6c` converts +it to `subagent_type`) but the canonical form is `agent_name`. There is **no +`history=`, no `fork_turns=`, no `context_size=`** — the calling agent has full +control of what the sub-agent sees by writing it into the `prompt` string. So +the 3 fork modes (all / N / none) become a `prompt` content decision, not a +parameter. + +## mcode 0.2.4 sub-agent types + +The mcode 0.2.4 `task` tool accepts three sub-agent types as the value of +`agent_name=`. The on-disk set of valid sub-agent manifests is verified +best-effort from the active mcode install in the static check +(`test/codex-harness-patterns.test.mjs` round-4 #3); a host-internal +manifest path is not part of the public runtime contract and is not +documented here. + +| `agent_name` | Tools | Use when | +|---|---|---| +| `explore` | `read`, `grep`, `glob`, `web_fetch` | Read-only investigation; cannot write or run commands. | +| `worker` | `read`, `write`, `edit`, `bash`, `grep`, `glob`, `todowrite`, `web_fetch`, `website_deploy` | Implementation; full read/write/run. | +| `verifier` | `read`, `grep`, `glob`, `bash`, `web_fetch` | Has `bash` but **no `write` / `edit` / `website_deploy`**: can run checks, cannot modify. | + +`mavis` is the **root** agent (different layout: `modes/`, `skills/`, +persona files). It is not an `agent_name` value; the calling session +already *is* mavis. The v0.2.0 list that included `mavis` as a sub-agent +option is removed. + +## When to use + +Activate when **any** of these is true: + +- You are about to call `task` (or any sub-agent spawn) to hand off a sub-task. +- You are designing a multi-agent flow (`parallel-fanout`, `delegate-with-context`). +- A previous sub-agent failed and you are debugging whether the cause was over- or + under-forking. +- You are about to spawn a sub-agent and feel unsure whether to pass context or not. + +## When NOT to use + +- The sub-agent tool does not accept any context / fork parameter (then the decision + is forced; skip). +- The sub-task is so trivial that the cost difference is noise (a one-line `read`). +- You have already decided "no context" (no decision to make). + +## The decision + +Three choices, ordered by cost. Each is implemented by what you write into +the `prompt` field. + +| Choice | What the sub-agent sees (in `prompt`) | Use when | +|---|---|---| +| **`all`** | The full parent conversation history, inlined or attached. | Sub-agent must reason about a prior decision, debug an earlier failure, or reuse a result the parent has already computed. | +| **`N`** (integer) | The last N turns, inlined. | Sub-agent needs recent context but not the full history. | +| **`none`** (or `0` / `brief`) | Only the brief you write inline. | Sub-task is self-contained; the brief is enough. | + +### Pseudo-cost table + +| Choice | Token cost | Sub-agent accuracy on context-dependent tasks | Sub-agent accuracy on self-contained tasks | +|---|---|---|---| +| `all` | 100% | high | low (distracted by noise) | +| `N` | moderate | high (if N is enough) | high | +| `none` | minimal | low | high (focused) | + +## Process + +1. **Classify the sub-task**. Does it need to see any prior turn? + - If **yes**: choose `all` or `N`. + - If **no**: choose `none` and write a self-contained brief. +2. **If you chose `N`**: pick the smallest N that still works. + - Start at 3. If the sub-agent asks for more context, bump to 5, then 10, then `all`. +3. **Build the `prompt`** for the chosen level: + - `all` → concatenate the entire prior conversation, then append the brief. + - `N` → concatenate the last N turns verbatim, then append the brief. + - `none` → just the brief, no prior content. +4. **Document the choice in the brief**: + - `Context level: ` + - `Reason: ` +5. **If the sub-agent fails**, retry with the next higher N before changing anything + else. A `none` that failed is almost always a brief problem, not a context + problem — but try `N=3` first because the cost is small. + +## Output contract + +After activating this Skill, the next `task` call MUST: + +- Pick a `agent_name` from `{explore, worker, verifier}` based on what the + sub-task needs (read / write+run / run-only). +- Include a `description` (3-5 word label). +- Build the `prompt` according to the chosen context level. +- Either inline the context (for `N` or `all`) or start the `prompt` with the + brief header: + +```text +# Sub-task brief +Context level: +Reason: +Sub-agent type: + +``` + +## Common pitfalls + +- **Defaulting to `all` "to be safe"** — costs you every turn, and dilutes the + sub-agent's focus. Only use `all` if you have a concrete reason. +- **Defaulting to `none` "to save cost"** — the sub-agent re-derives from the brief, + and the brief is often wrong. The cost saved is the cost of the bug. +- **Not documenting the choice** — a future reviewer (or you, tomorrow) cannot tell + why `N=3` was chosen. Document or it didn't happen. +- **Changing the brief without changing `N`** — if the brief is wrong, more context + doesn't help. Fix the brief first. +- **Writing a single tool call expecting the host to manage history** — mcode 0.2.4 + does not auto-attach prior conversation. The decision is in the call you write. +- **Using `agent_name="mavis"`** — mavis is the root agent, not a sub-agent. + Use `explore` / `worker` / `verifier`. + +## Example + +The example below uses **MiniMax Code 0.2.4 `task` tool syntax**. The 3 fork +modes are demonstrated; the `prompt` content is what changes between them. + +```text +# Context level: none +# Sub-agent: worker (writes files) +# Cost: minimal +> task( + description="Investigate lint flake", + agent_name="worker", + prompt=""" + Context level: none + Reason: this is a self-contained repro request. + Sub-agent type: worker + + Investigate why /tests/test_lint.py line 47 + flakes on Windows but not Linux. Write a 1-paragraph + root-cause analysis to /notes/lint.md under + "## Windows flake root cause". + """ + ) + +# Context level: N=3 +# Sub-agent: worker +# Cost: 3 prior turns inlined +> task( + description="Diagnose test failure", + agent_name="worker", + prompt=""" + Context level: 3 + Reason: the previous tool output is the most likely + cause; the sub-agent needs to see it. + Sub-agent type: worker + + === last 3 turns (verbatim) === + + + + + Investigate the line 47 failure and write a fix to + /tests/test_lint.py. + """ + ) + +# Context level: all +# Sub-agent: explore (read-only) +# Cost: 100% of parent context +> task( + description="Audit earlier decision", + agent_name="explore", + prompt=""" + Context level: all + Reason: the sub-agent must reason about a decision + made 12 turns ago. + Sub-agent type: explore + + === full prior conversation (verbatim) === + + + Find every place we used 'json.dumps(indent=2)' and + confirm the output matches the user's earlier spec. + """ + ) +``` + +The **decision** (3 turns vs full history vs brief only) is the same; the +**implementation** is what you put in the `prompt` string. + +## Verification checklist + +- [ ] Did you classify the sub-task before choosing? +- [ ] Did you pick the smallest N that works (not jumping straight to `all`)? +- [ ] Did you pick a `agent_name` from `{explore, worker, verifier}`? +- [ ] Did you document the context level in the brief header? +- [ ] Did you build the `prompt` so the sub-agent actually sees the chosen context? +- [ ] If the sub-agent failed, did you bump N before changing the brief? diff --git a/plugins/antianqi/codex-harness-patterns/skills/goal-persistence/SKILL.md b/plugins/antianqi/codex-harness-patterns/skills/goal-persistence/SKILL.md new file mode 100644 index 0000000..2443b00 --- /dev/null +++ b/plugins/antianqi/codex-harness-patterns/skills/goal-persistence/SKILL.md @@ -0,0 +1,270 @@ +--- +name: goal-persistence +description: | + Maintain explicit north-star goal for the whole thread that survives compactions and detects drift. + USE WHEN: non-trivial task stated, user redirected mid-task ("actually do X instead" / "wait scrap that" / "现在改成"), before `context-pressure-compact`, about to mark done, user said "我们的目标是" / "we're trying to" / "我想要的" / "what I want is" / "目标是", agent drifting (tool call no longer serves original ask). + TRIGGER PHRASES: "我们的目标", "目标是", "我想要", "we're trying to", "what I want is", "drift", "走偏了", "focus on", "stay focused", "on track", "actually do X instead", "wait scrap that", "现在改成". + SKIP WHEN: trivial one-shot task, exploration without commitment, goal hasn't changed in many turns. +license: Apache-2.0 +compatibility: Requires MiniMax Code with Agent Plugins 1.0 support. +metadata: + author: antianqi + version: "1.0.1" + inspired-by: https://github.com/openai/codex/blob/main/codex-rs/protocol/src/protocol.rs (SetThreadMemoryMode, ThreadGoalUpdatedEvent) and ext/goal/templates/goals/continuation.md + changes-from-v0.1.0: "Added completion-audit and blocked-audit sections from the Codex continuation template; added token-budget reporting rule; aligned language with the canonical 'treat completion as unproven' principle." +--- + +# Goal Persistence + +The single biggest reason long tasks fail is **goal drift**: the agent starts doing A, the user +asks for B, the conversation accumulates noise, the agent ends up doing C with the +justification that "it felt like the right next step." The original goal is gone — or worse, +silently replaced by a goal the agent inferred. + +This Skill keeps the original goal *visible*, *versioned*, and *checkable* across the whole +thread. It is the *why* of the task; `world-state-tracking` is the *where*. + +**v1.0 update**: now incorporates the canonical completion audit and blocked audit +from the Codex goal continuation template, so declaring "done" is always evidence-based, +not intent-based. + +## When to use + +Activate when **any** of these is true: + +- A non-trivial task has just been stated (one-time **set**). +- The user has redirected the task ("actually, do X instead", "wait, scrap that", "now also + include Y") — one-time **update**. +- A `context-pressure-compact` is about to be applied — one-line **alignment check**. +- The agent is about to start a tool call that has *any* chance of being misaligned with + the original ask (a "drift self-test"). +- The agent is about to mark the goal as `complete` or `blocked` — the **completion audit** + and **blocked audit** sections apply. + +## When NOT to use + +- Trivial one-shot tasks. The user request *is* the goal; no need to persist it. +- Pure research / exploration ("look into X, no commitment"). A goal implies a deliverable. +- The goal has not changed in many turns and the agent is on track. Re-writing the goal + file is noise. + +## Process + +1. **Pick a single, predictable path.** Default: + `.minimax/goal/-.md`. Different from the world-state file (which is + "where we are"; this is "what we are doing"). +2. **Initialise the goal file** at the start of a non-trivial task, in this exact shape: + + ```markdown + # Goal — + + **Set**: + **Owner**: + **Last checked**: + **Version**: 1 + + ## Original goal (verbatim from the user) + + " if + verbatim is impractical> + + ## Why this goal + + + + ## Success looks like + + - + - + + ## Explicitly out of scope + + - + - + + ## Version history + + - v1: — initial set + ``` + +3. **Update the goal** (bump `Version`, append a row to Version history) when **any** of: + - The user explicitly redirects. + - The user adds or removes a deliverable. + - The user expands or narrows the scope. + - The user re-states the goal in a way that supersedes the prior version. +4. **Drift self-test** before any non-trivial tool call: read the goal file, read the + tool call, ask "does this tool call serve the current version of the goal?". If + **no**, surface the drift to the user before executing: + + ```text + Drift check: this tool call is ``, but the current goal is ``. + - aligned → continue + - misaligned (tool call is a side quest) → ask the user before executing + - superseded (the goal has moved on) → update the goal file first + ``` + +5. **At every `context-pressure-compact`**, the compact summary must reference the goal + file by path, not duplicate it. The goal file is the thing that survives; the + summary is the thing that gets re-derived. +6. **Before marking the goal `complete`**, run a **completion audit** (next section). +7. **When the user finally says "done" / "ship it" / "looks good"**, mark the goal as + achieved in the file (`Status: achieved, `) and leave the file in place as part + of the audit trail. **On a budgeted goal, also report the final token usage to the + user** (token accountability). + +## Completion Audit (before declaring done) + +**Treat completion as unproven until you have evidence for each requirement.** + +```text +Verifying before declaring "" done. + +| Requirement | Evidence | Result | +|--------------------------------------------|---------------------------------------------------|--------| +| | | ✅ | +| | | ✅ | +| ... | ... | ... | +``` + +Result legend: ✅ proves completion · ❌ contradicts · 🟡 incomplete · ⚪ too weak · 🚫 missing. + +**All items must be ✅ before declaring done.** If any item is not ✅, surface the unfinished +items; do not mark complete. See `completion-audit` Skill for the full protocol. + +## Blocked Audit (before declaring blocked) + +**Do not declare blocked the first time a blocker appears.** Only use `blocked` when the +same blocking condition has repeated for at least **three consecutive goal turns** (the +original/user-triggered turn plus any automatic continuations), and the agent is at a true +impasse. + +```text +Checking if "" should be marked blocked. + +- Turn N: blocker = ← not yet +- Turn N+1: blocker = ← not yet +- Turn N+2: blocker = ← not yet +- Turn N+3: blocker = ← THRESHOLD MET, can mark blocked + +If after 3 turns the blocker is different, reset the count. +``` + +**Do not mark blocked merely because the work is hard, slow, uncertain, incomplete, or would +benefit from clarification.** "I don't know what to do next" is not blocked — it is +uninformed, and the response is to ask, not to stop. + +## Token Budget Reporting (on a budgeted goal) + +If the goal has a `token_budget`, when marking `complete` (or `blocked`): + +```text +Final token usage: 18,420 / 20,000 (92% of goal budget). +``` + +The user set the budget; they get the report. Do not omit the final number; do not estimate — +read it from the actual usage. + +## Output contract + +The user sees, in this order: + +- On set: the goal file's contents (full) + the path + the version. +- On update: the diff (one line: "v1 → v2: "). +- On drift check: one line verdict (`aligned` / `misaligned: ` / `superseded: `). +- On compact: a one-line "Goal still in scope, see ". +- Before done: the completion audit table + final token usage (if budgeted). +- Before blocked: the blocked audit count + the actual blocker. +- On "done": the goal file marked `Status: achieved, `. + +## Example goal file + +```markdown +# Goal — Auth refactor (OIDC alongside SAML) + +**Set**: 2026-08-23 +**Owner**: main +**Last checked**: 2026-08-23T23:55:00Z +**Version**: 1 + +## Original goal (verbatim from the user) + +> "Refactor the auth subsystem to support OIDC without breaking the existing SAML path." + +## Why this goal + +The user is migrating from a single-SAML IdP to multi-IdP (SAML + OIDC) to support a new +customer segment. They cannot break the existing 12 SAML tests because that would +regress two production customers. The OIDC work is for *new* customers only. + +## Success looks like + +- A new OIDC provider implementation that works end-to-end with one real-world IdP + (e.g. Okta). +- All 12 existing SAML tests still pass. +- A config flag `auth.oidc_enabled` defaults to `false`, so production is unaffected. +- One happy-path test for OIDC login with a mock IdP. + +## Explicitly out of scope + +- Refactoring the existing SAML code beyond what is strictly necessary to add the + provider abstraction. +- Adding OAuth2 (not OIDC) flows. +- Changing the session storage layer. + +## Version history + +- v1: 2026-08-23T22:00:00Z — initial set +``` + +Drift check example: + +```text +> bash(command="git rebase --interactive HEAD~20", description="rewrite recent history") + +Drift check: this tool call is "rewrite 20 commits of history", but the current goal +is "add OIDC without breaking SAML". +- misaligned (interactive rebase is not on the path to the goal) → confirm with the + user before executing +``` + +## Common pitfalls + +- **Do not skip the "why this goal" section.** It is the most valuable paragraph. It + is the guard against drift: when in doubt, the "why" disambiguates. +- **Do not paraphrase the original goal** unless verbatim is impractical. Paraphrase + loses nuance; the user might have picked those exact words for a reason. +- **Do not let the goal file grow.** A 200-line goal file is a project plan, not a + goal. Keep it under ~40 lines; let `world-state-tracking` and `todowrite` carry the + detail. +- **Do not drift-check every tool call.** A drift check before `read` or `grep` is + noise. Drift-check before any *write*, *edit*, or *bash* that has a non-trivial + surface. +- **Do not update the goal on every turn.** Goal updates are rare events. If you are + bumping the version more than once per 20 turns, you are not using it as a goal. +- **Do not conflate goal with state.** The goal file is *what*; the world-state file + is *where*. They are different files for different questions. +- **Do not mark complete without a completion audit.** "I think it works" is not + evidence. Each requirement needs its own ✅. +- **Do not mark blocked at the first blocker.** Three consecutive turns of the same + blocker is the threshold. "Hard" is not "blocked." +- **Do not omit token usage on a budgeted goal.** The user set the budget to know what + the work costs; they get the final number. + +## Verification checklist + +- [ ] Did you pick a single, predictable path for the goal file? +- [ ] Is the goal file under ~40 lines? +- [ ] Does it have all sections (Set / Owner / Last checked / Version / Original + goal / Why this goal / Success / Out of scope / Version history)? +- [ ] Is the "Original goal" copied verbatim where possible? +- [ ] Does the "Why this goal" paragraph explain motivation, not just the surface + request? +- [ ] Did you do a drift self-test before the last non-trivial tool call? +- [ ] At the next `context-pressure-compact`, does the summary reference the goal + file by path? +- [ ] Before marking done, did you run the completion audit (all items ✅)? +- [ ] Before marking blocked, did you count to 3 consecutive turns of the same + blocker? +- [ ] On done, did you report final token usage (if budgeted)? +- [ ] On done, did you mark the goal as achieved in the file (audit trail)? diff --git a/plugins/antianqi/codex-harness-patterns/skills/goal-token-budgeting/SKILL.md b/plugins/antianqi/codex-harness-patterns/skills/goal-token-budgeting/SKILL.md new file mode 100644 index 0000000..8e40fb6 --- /dev/null +++ b/plugins/antianqi/codex-harness-patterns/skills/goal-token-budgeting/SKILL.md @@ -0,0 +1,156 @@ +--- +name: goal-token-budgeting +description: | + Track running token usage against goal's `token_budget`, surface at 50/80/100% thresholds, stop at 100%. + USE WHEN: `goal-persistence` active AND user provided `token_budget`, user said "do X within Y tokens" / "用 Y token 完成" / "不要超预算" / "stayed within budget" / "超出预算" / "用了多少 token", about to start sub-task and need to know remaining budget, at every compact / turn boundary. + TRIGGER PHRASES: "token budget", "预算", "Y tokens", "不要超过", "stayed within budget", "超出预算", "用完没", "用了多少 token", "预算跟踪", "50% / 80% / 100%", "token 预算". + SKIP WHEN: goal has no budget (user did not set one), user explicitly said "no budget tracking for this one". +license: Apache-2.0 +compatibility: Requires MiniMax Code with Agent Plugins 1.0 support. +metadata: + author: antianqi + version: "0.1.1" + inspired-by: https://github.com/openai/codex/blob/main/codex-rs/ext/goal/src/accounting.rs and ext/goal/templates/goals/continuation.md +--- + +# Goal Token Budgeting + +A goal without a budget is open-ended; an agent will spend whatever it takes to "look done" +at the cost of the user. A goal **with** a budget is a contract: the agent is accountable +for fitting the work inside the spend, and the user can decide whether more spend is +worth it. + +This Skill tracks the running usage against the budget, surfaces the trend, and reports +the final number on completion. + +## When to use + +Activate when **any** of these is true: + +- `goal-persistence` is active and the user provided a `token_budget` when setting the goal. +- The user says "do X within Y tokens" / "use the cheap model for this" / "don't burn too + much on this." +- You are about to start a sub-task and want to know "how much budget do I have left?" +- A `context-pressure-compact` is about to be applied and you need to report whether the + goal is still within budget. + +## When NOT to use + +- The goal has no budget (the user didn't set one). Tracking zero is not useful. +- The user explicitly said "no budget tracking for this one." + +## Process + +1. **At goal set**, if the user provided a `token_budget`, record it in the goal file + (`goal-persistence` Skill) under a new section: + + ```markdown + ## Token budget + + **Budget**: (set by user) + **Set at**: + ``` + + If the user did not provide a budget, do not add this section. Absence of the section + means "no budget." + +2. **At every turn boundary** (end of your response, or at every `context-pressure-compact`), + read the harness's per-turn token usage and append a row to a usage log: + + ```markdown + ## Usage log + + | turn | tokens | used_so_far | remaining | % of budget | + |------|--------|-------------|-----------|-------------| + | 1 | 1,240 | 1,240 | 18,760 | 6% | + | 2 | 2,100 | 3,340 | 16,660 | 17% | + | ... | ... | ... | ... | ... | + ``` + + You can read the harness's token usage from: + - the conversation transcript (if the harness surfaces per-turn counts) + - the system prompt (if it includes running totals) + - `TokenCount` events (if the harness emits them — see `protocol::EventMsg::TokenCount`) + +3. **At every boundary, surface the trend**, not just the raw number: + + - **Under 50% used**: silent (the budget is fine). + - **50-80% used**: mention the budget in the response, one line. "Budget: 12,400 / 20,000 + (62%)." + - **80-100% used**: warn the user explicitly. "Budget almost used: 17,200 / 20,000 (86%). + Next turn may exceed — should I stop here or continue?" + - **Over 100% used**: stop, surface to user, ask whether to: + - declare the goal done with the over-budget cost + - ask for a budget extension + - re-scope the goal to fit + +4. **At every `context-pressure-compact`**, the compact summary must include the latest + usage row. The user must be able to see the budget trend across the compacted summary. + +5. **On goal completion** (via `completion-audit` Skill), the final report **must** include + the actual final token usage, read from the harness — not an estimate. The user set the + budget; they get the number. + +## Output contract + +The user sees: + +- At goal set: a one-line "Budget: N tokens" in the goal file. +- During execution: silent under 50%, one line at 50-80%, warn at 80-100%, stop at 100%+. +- At every compact: the latest usage row included in the summary. +- On completion: a one-line "Final token usage: M / N (X%)". + +## Example + +```text +> User: "Migrate the auth subsystem to OIDC alongside SAML. Token budget: 20,000." + +[Goal file] +## Token budget +**Budget**: 20,000 tokens (set by user) +**Set at**: 2026-08-23T22:00:00Z + +[Usage log over time] +| turn | tokens | used_so_far | remaining | % of budget | +|------|--------|-------------|-----------|-------------| +| 1 | 1,240 | 1,240 | 18,760 | 6% | +| 2 | 2,100 | 3,340 | 16,660 | 17% | +| 5 | 3,200 | 8,940 | 11,060 | 45% | +| 8 | 2,500 | 14,300 | 5,700 | 71% ← one-liner appears | +| 10 | 1,800 | 17,200 | 2,800 | 86% ← warn appears | +| 11 | 1,500 | 18,700 | 1,300 | 93% ← warn continues | +| 12 | 1,400 | 20,100 | (over) | 101% ← stop and ask user | +``` + +At turn 12, the agent surfaces: + +> Budget exceeded: 20,100 / 20,000 tokens. I have 2 of the 4 success criteria verified. +> Options: (a) declare partial completion, (b) ask for a 5,000-token extension, (c) re-scope +> the goal to what fits in 2,000 more tokens. Which? + +## Common pitfalls + +- **Do not invent a budget the user did not set.** Absence of budget is not "use whatever + you need." It is "track usage but do not warn at thresholds." +- **Do not estimate the final number on completion.** Read the actual usage from the + harness. Estimation is dishonest. +- **Do not warn at 50% if the user explicitly said "don't bother me with budget updates."** + Respect the user's signal. +- **Do not silently cross 100%.** Stop and ask. Crossing the budget without surfacing it + is a betrayal. +- **Do not re-scope the goal unilaterally to fit the budget.** Re-scoping is a *user + decision*. The agent's only job at 100%+ is to surface the situation. +- **Do not skip the budget tracking because "the task is small."** A small task becoming + a 5× budget overrun is exactly when tracking matters most. + +## Verification checklist + +- [ ] Did the user set an explicit `token_budget`? (If not, do not activate this Skill.) +- [ ] Is the budget recorded in the goal file under "Token budget"? +- [ ] Does the usage log get a row at every turn boundary? +- [ ] At 50-80%, is there a one-line mention in the response? +- [ ] At 80-100%, is there an explicit warn to the user? +- [ ] At 100%+, did the agent stop and ask the user (not silently continue)? +- [ ] At every compact, is the latest usage row in the summary? +- [ ] On completion, is the final number the actual harness-reported usage (not an + estimate)? diff --git a/plugins/antianqi/codex-harness-patterns/skills/long-term-memory/SKILL.md b/plugins/antianqi/codex-harness-patterns/skills/long-term-memory/SKILL.md new file mode 100644 index 0000000..b017901 --- /dev/null +++ b/plugins/antianqi/codex-harness-patterns/skills/long-term-memory/SKILL.md @@ -0,0 +1,164 @@ +--- +name: long-term-memory +description: | + Design a cross-session long-term memory system that extracts, consolidates, and cites durable knowledge from conversation rollouts. + USE WHEN: building any system that needs to persist insights across sessions, designing "what should the next agent remember" pipelines, building memory workspaces with git baseline diffing, planning Phase 1/Phase 2 memory architectures, writing JSON-schema-constrained extraction prompts, deciding what NOT to write (no-op gate), or any task involving "memories that survive session boundaries". + TRIGGER PHRASES: "long-term memory", "cross-session memory", "memory pipeline", "memory consolidation", "memory citation", "raw_memories.md", "MEMORY.md", "phase 1 extraction", "phase 2 consolidation", "watermark", "no-op gate", "git baseline diff". + SKIP WHEN: single-session task state (use `world-state-tracking` instead), ephemeral/short task, no need to survive session boundaries, in-memory only. +license: Apache-2.0 +compatibility: Requires MiniMax Code with Agent Plugins 1.0 support. +metadata: + author: antianqi + version: "0.1.0" + inspired-by: https://github.com/openai/codex/tree/main/codex-rs/memories/ and protocol/src/memory_citation.rs + changes-from-v0.0.0: "Initial design distilled from P-78/79/80/84 deep-dive (Phase 1 Week 1)." +--- + +# Long-Term Memory + +Design and operate a cross-session long-term memory system that survives session +boundaries. Mirrors the structure of Codex's `codex-rs/memories/` crate. + +## When to use + +Activate when designing any of: + +- A pipeline that extracts structured facts from conversation rollouts and writes them to durable storage. +- A global consolidation pass that merges per-rollout facts into higher-level summaries without races. +- A citation protocol so a future agent can audit which memory came from which rollout. +- A "no-op gate" — the system MUST be allowed to write nothing when there is no durable learning. + +## When NOT to use + +- Single-session task state → use `world-state-tracking`. +- Real-time voice / streaming → out of scope. +- Forgetting-on-purpose privacy filters → out of scope. + +## Host runtime requirements + +This Skill describes **how to design** a cross-session memory system (Phase 1 +extraction, Phase 2 consolidation, citation format, git baseline, watermark). +It does **not** cause the agent to install, modify, or write to persistent +storage on its own. Specifically, the agent MUST NOT, on the strength of this +Skill alone: + +- Read or write files in `~/.codex/memories/`, `~/.minimax/memory/`, or any other + per-host memory workspace. **No memory directory is implicitly writable by + the agent.** +- Spawn sub-agents or background tasks to perform extraction / consolidation. +- Trigger a Phase 1 / Phase 2 schedule on session start (the host decides when + memory runs; this Skill does not). +- Call `redact_secrets` or any other exfiltration-mitigation step without the + host's normal user-confirmation policy. + +All of the above require **explicit user confirmation** in the host's normal +permission flow (`approval_policy`, `ask` mode, or whatever the host uses). +This Skill is for **designing** the pipeline, not for **executing** it. The +agent that runs Phase 1 / Phase 2 must follow the host's user-confirmation +policy, **not** the patterns in this Skill. + +## Process + +A long-term memory system is built from four pieces. Build them in this order. + +### 1. Phase 1 — per-rollout extraction (parallel, idempotent) + +The writer of memories. Runs at session start (or on a schedule), claims bounded jobs from a queue, and for each: + +- Loads the rollout (JSONL or DB-backed). +- Filters to memory-relevant response items. +- Prompts a model with a JSON schema producing `{raw_memory, rollout_summary, rollout_slug}`. +- Redacts secrets from the output. +- Persists to durable storage (DB row or file). + +Hard rules: + +- `#[serde(deny_unknown_fields)]` on the output struct so the model cannot add fields. +- Concurrency capped by a single constant (e.g. `CONCURRENCY_LIMIT = 8`). Use `futures::stream::iter(...).buffer_unordered(N)`. +- Lease/ownership token prevents two workers from re-extracting the same rollout. +- If a job fails, record the failure with a backoff; do not hot-loop. +- **Allow no-op**: the prompt MUST include the question "Will a future agent plausibly act better because of what I write here?" and an empty-output escape hatch. If the answer is no, write nothing. + +### 2. Phase 2 — global consolidation (serial, single lock) + +The reader of stage-1 outputs. Runs at session start, after Phase 1, with one global lock so two Codexes never consolidate simultaneously. + +- Load top-N stage-1 outputs ranked by `usage_count` then `last_usage` (fallback `generated_at`). +- Filter by `last_usage >= now - max_unused_days` (otherwise stale). +- Sync the selected inputs into a workspace as `raw_memories.md` (ascending thread-id order, never usage-rank) and `rollout_summaries/.md`. +- Prune stale rollout summaries and old extension resources. +- **Use git baseline as a cheap state machine**: `~/.codex/memories/.git/` keeps a `git diff` against the previous successful baseline. If there are no changes, mark success and exit. +- If there ARE changes, write `phase2_workspace_diff.md` and spawn an **internal consolidation sub-agent** with these hard constraints: + - `cwd` = the memory root only. + - `ephemeral = true`. + - `features.disable(Collab / MemoryTool / Apps / Plugins)`. + - `approval_policy = Never`. + - `network_access = false` (or inheriting parent's `PermissionProfile::External`). + - **Disabled from re-entering Phase 1**: `memories.generate_memories = false` and `use_memories = false`. + +### 3. MemoryCitation protocol + +When the model emits memory, it should be able to point at exact lines. Adopt this single-line format: + +``` + +path/to/file.md:10-15 |note=[why this matters] +path/to/other.md:42-50 |note=[other context] + + +thread-abc-123 +thread-def-456 + +``` + +Parse with `split_once` × 3 (location / `|note=[` / `]`). `try_from().ok()` style tolerance for malformed lines. De-duplicate `rollout_ids` with a `HashSet`. + +### 4. Watermark + +After successful Phase 2, write `new_watermark = max(claimed_watermark, max(source_updated_at))` to the DB. **Watermarks are monotonically increasing** — never move backwards. They are bookkeeping, not the dirty check (git workspace is). + +## Output contract + +A working long-term memory system should produce: + +- `~/.codex/memories/MEMORY.md` — consolidated memory (Phase 2 agent writes). +- `~/.codex/memories/memory_summary.md` — first line is `v1` (version marker). +- `~/.codex/memories/raw_memories.md` — per-rollout raw memories in stable ascending thread-id order. +- `~/.codex/memories/rollout_summaries/.md` — one per selected rollout. +- `~/.codex/memories/phase2_workspace_diff.md` — temporary, deleted before baseline reset. +- `~/.codex/memories/.git/` — git baseline for cheap state machine. + +## Common pitfalls + +- **No-op gate skipped** → model hallucinates low-signal memories every session; memory file grows unbounded. The prompt MUST force the self-question. +- **Stable-key churn** → ordering by `usage_count` causes git to show a "change" every run even when content didn't change. Order by thread-id instead. +- **Reset baseline with diff present** → deleted content stays in git objects forever. Always remove `phase2_workspace_diff.md` BEFORE `reset_git_repository`. +- **Two Codexes consolidating simultaneously** → corruption. Use a single global lock, not optimistic concurrency. +- **Sub-agent with collab enabled** → infinite recursion. Disable `Feature::Collab` on the consolidation agent. +- **Sub-agent with network** → privacy leak. Force `network_access: false` in the sandbox policy. +- **No secrets redaction** → API keys in memory. Always call `redact_secrets` on model output before persisting. +- **Watermark moved backwards** → duplicate work. Use `max(claimed, max(newest_input))`. + +## Example — minimal memory workflow + +```text +# At session start +phase1::run(claimed_jobs) # parallel, schema-constrained, redacted, leased +phase2::run(claim_global_lock) # serial, single global lock + if !git_diff.has_changes() { mark_success_no_workspace_changes; return; } + write_workspace_diff(...) + spawn_consolidation_agent(ephemeral, no_collab, no_network, no_memory_tool) + handle(lease_heartbeat, validate_artifacts, reset_baseline, mark_succeeded) +``` + +## Verification checklist + +- [ ] Phase 1: `deny_unknown_fields` schema; `buffer_unordered` concurrency cap; lease + ownership token; `redact_secrets`. +- [ ] Phase 1: prompt includes the "future agent plausibly act better" question and the empty-output escape. +- [ ] Phase 2: single global lock (DB lease or file lock); retry with backoff; never two simultaneous runs. +- [ ] Phase 2: spawn sub-agent with `ephemeral + features.disable(Collab) + no network + no memory tool`. +- [ ] Workspace: `raw_memories.md` is sorted by ascending thread-id, never by usage rank. +- [ ] Workspace: `phase2_workspace_diff.md` is removed BEFORE `reset_git_repository`. +- [ ] Watermark: monotonically increasing, never moves backwards. +- [ ] Citation: single-line `:- |note=[]` format; `try_from().ok()` tolerance. +- [ ] Trigger Phase 1/2 ONLY for non-ephemeral, non-sub-agent root sessions. diff --git a/plugins/antianqi/codex-harness-patterns/skills/model-router/SKILL.md b/plugins/antianqi/codex-harness-patterns/skills/model-router/SKILL.md new file mode 100644 index 0000000..f0e6f9d --- /dev/null +++ b/plugins/antianqi/codex-harness-patterns/skills/model-router/SKILL.md @@ -0,0 +1,205 @@ +--- +name: model-router +description: | + Classify sub-task complexity (cheap / medium / main) and decide whether to spawn a sub-agent at all. On MiniMax Code 0.2.4 the `task` tool does not expose per-call model selection, so the 3-tier rubric here is a thinking framework for session-level model choice and a "do I really need a sub-agent?" gate, not a per-call `model_config_id` field. + USE WHEN: about to spawn a sub-agent for non-trivial work, about to spend the main model on something a cheap model could do, "do this with the cheap model" / "用便宜模型" / "不要用主模型" / "sub-task 不重" / "small task" / "小任务". + TRIGGER PHRASES: "用便宜模型", "cheap model", "use the cheap model", "小任务用便宜模型", "不要用主模型", "用本地模型", "sub-task 不重", "小任务", "this is just a", "小 case 用便宜". + SKIP WHEN: sub-task IS the main task, mcode 0.2.4's `task` tool does not expose a per-call model field, sub-task is genuinely synthesis / design / cross-file reasoning. +license: Apache-2.0 +compatibility: Targets MiniMax Code 0.2.4. **The mcode 0.2.4 `task` tool does NOT expose a `model_config_id` parameter or any other per-call model-routing field.** Verified against the bundled `cli.js` canonical schema (only `description` / `prompt` / `agent_name` / `run_in_background`). Model selection on mcode 0.2.4 is **session-level** (chosen at session start via the host's `model` flag / interactive picker). This Skill therefore reframes the original 3-tier rubric into a session-level thinking framework and a sub-agent gate, not a per-`task()` argument. +metadata: + author: antianqi + version: "0.4.0" + inspired-by: https://github.com/openai/codex/blob/main/codex-rs/model-provider-info/ and codex-rs/models-manager/ (design principle only; the 3-tier classification is portable; the mcode 0.2.4 surface for model selection is session-level, not per-task) + changes-from-v0.3.3: "Removed the v0.3.3 claim that 'MiniMax Code's `task` tool accepts `model_config_id` directly' — that was wrong. The canonical mcode 0.2.4 `task` schema has no per-call model field; the only model-routing surface on 0.2.4 is session-level (the host's `model` config at session start). The 3-tier rubric is preserved as a thinking framework and as a sub-agent gate ('don't spawn a sub-agent if the work is `cheap` enough that the calling session's current model could do it in 2 tool calls'), but no `model_config_id` is passed in any `task()` call. The Example section is reframed to match the actual mcode 0.2.4 surface." +--- + +# Model Router + +The main model is expensive and slow. Most sub-tasks a long agent spawns are not +"main-model expensive" — they are lookups, transforms, summaries, or pattern matches. The +Codex harness routes those to cheaper models and reserves the main model for synthesis and +hard reasoning. + +On **mcode 0.2.4** specifically, the `task` tool does **not** expose a per-call model +parameter. The 3-tier rubric below is therefore a **thinking framework for session-level +model choice and a sub-agent gate**, not a per-`task()` argument. The Skills keep the +classification because the cost-of-thought question is the same; they just do not pretend +mcode 0.2.4 routes per call. + +## The hard fact about mcode 0.2.4 + +The canonical mcode 0.2.4 `task` schema: + +```text +task( + description: string, // required + prompt: string, // required + agent_name: "explore" | "worker" | "verifier", // required + run_in_background?: boolean // optional +) +``` + +**No `model_config_id`, no `model`, no `reasoning_effort`, no per-call tier.** Model +selection on mcode 0.2.4 is session-level (chosen at session start via the host's +`model` flag / interactive picker — the same model is used for the whole session, +including every `task` call). Trying to pass `model_config_id="..."` is rejected +by the strict validator in `cli.js:B6c` (the only allowed keys are the four above). + +The v0.3.3 wording "MiniMax Code's `task` tool accepts `model_config_id` directly" +was wrong. The cost-of-thought question (cheap / medium / main) is still worth +asking; just not as a per-`task()` argument. + +## When to use + +Activate when **any** of these is true: + +- You are about to spawn a sub-agent and the work is non-trivial. +- You are about to spend the main model on a work step that has clearly bounded + complexity (a lookup, a transform, a reformat, a coverage report). +- A sub-task failed and you are about to retry; consider whether the brief was + bad, not whether a stronger model would help (mcode cannot route per call). +- A batch of N similar sub-tasks is about to run; classify them once and decide + whether to spawn at all (the sub-agent gate). + +## When NOT to use + +- The sub-task *is* the main task (no delegation happening). You are already on + the right model. +- The sub-task requires the same context the main thread has, and you cannot + pass a minimal-context brief (see `fork-context-decision`). A sub-agent with + no context will fail — do it yourself. +- The user explicitly said "use the main model for this" or "don't downgrade the + model" (no-op on mcode 0.2.4 anyway, but respects the user's framing). + +## Process + +1. **Classify the sub-task** into one of three tiers, before deciding whether to + spawn a sub-agent at all: + + | Tier | When to use | Examples | + |---|---|---| + | **`cheap`** | Bounded, single-shot, the brief fully specifies success. No synthesis, no judgement. | reformat a file, list files matching a glob, count lines, parse a JSON, run a deterministic script, copy a file with substitutions | + | **`medium`** | Multi-step but well-scoped, the brief is the only context needed. Some judgement, no synthesis of new ideas. | summarise a long doc, refactor a single function, write tests for a known spec, review a single PR | + | **`main`** | Requires synthesis, judgement across multiple sources, or stakes that make cheap-model mistakes costly. | design an API, evaluate tradeoffs, debug a multi-file interaction, write code that needs to satisfy a spec the agent has to interpret | + + If unsure, classify up — `main` is the safe default. + +2. **Decide whether to spawn at all** (the sub-agent gate, on mcode 0.2.4): + - If the work is **`cheap`** AND the calling session can do it in 2 tool calls + (one `read` / `grep` / `bash` + one `write` / nothing), **do not spawn**. + The cost of the spawn (the sub-agent's bootstrap, the brief round-trip) is + higher than just doing the work. + - If the work is **`medium`** or **`main`**, spawn with the appropriate + `agent_name` (`explore` / `worker` / `verifier`). + - The model that runs the sub-agent is the same as the calling session's + model — there is no per-call tier routing on mcode 0.2.4. + +3. **State the tier and the spawn decision in the sub-task brief** so a human + reviewer can see why you spawned (or did not spawn): + + ```markdown + ## Sub-task brief + ... + + **Tier**: cheap | medium | main + **Spawn decision**: doing-it-myself | task(agent_name=...) + **Reason**: + ``` + +4. **If the sub-task returns a "I can't do this"**, do not silently retry on the + same call. Re-classify: either the brief is wrong (rewrite it) or the work is + harder than the tier suggested (re-classify up). On mcode 0.2.4 "going up a + tier" means ending the session and restarting on a stronger model, not passing + a different parameter — call this out to the user. + +5. **Record the actual spend** if the harness surfaces per-call token counts. + After a fan-out, note in the aggregation how much of the total was cheap-vs- + medium-vs-main. This is how you learn the right tier for each sub-task shape. + +## Output contract + +The user sees, in this order: + +- For every spawn decision: the tier and the spawn reason (one line each). +- For the fan-out aggregation: a one-line "X cheap / Y medium / Z main" summary. +- For upgrades (cheap → medium → main on a retry): a one-line reason plus the + user-facing cost (on mcode 0.2.4: "this needs main; please restart the + session on a stronger model"). + +## Example + +The example below is **MiniMax Code 0.2.4 `task` tool syntax**. The +`model_config_id` argument is **deliberately not shown** because mcode 0.2.4 +does not accept it. + +```text +[planning] 1 cheap call: list all *.rs files in /src/auth/ that + import `tokio::sync::Mutex`. + — tier: cheap (deterministic glob + grep, no judgement) + — spawn decision: DO NOT SPAWN. 1 read + 1 grep is faster than + the sub-agent bootstrap. + +[execution] 1 medium call: refactor auth/callback.rs to extract the SAML + response parser. + — tier: medium (multi-step refactor, brief is the spec) + — spawn decision: task(agent_name="worker") + — model: same as calling session (mcode 0.2.4 has no per-call + model field) + +> task( + description="Refactor SAML parser", + agent_name="worker", + prompt=""" + Tier: medium + Spawn: task(agent_name=worker) + Task name: refactor-saml-parser + Sender: main agent + Task: Extract the SAML response parser from + /src/auth/callback.rs lines 80-140 into + /src/auth/saml.rs as a free function. + Payload: /src/auth/callback.rs (lines 80-140); + /src/auth/mod.rs (re-export point). + Return: Write the new file /src/auth/saml.rs and + update /src/auth/mod.rs. Run + `cargo test --lib auth` and confirm green. + """ + ) + +[execution] 1 main call: design the OidcProvider trait given the existing + IdP interface and the OIDC spec. Resolve the + "extend IdP vs new sibling" question. + — tier: main (synthesis + cross-source judgement) + — spawn decision: this is the main task; no spawn. + — model: session-level main model. +``` + +## Common pitfalls + +- **Do not default to main.** The default is the most expensive answer. The + skill exists to move work *off* main, not to confirm the obvious. +- **Do not route synthesis to cheap.** Synthesis requires judgement, cheap + models hallucinate on it, and you will pay more on the retry. +- **Do not classify by token count of the sub-task input.** Classify by *what + the sub-task is* (lookup vs synthesis). A 50,000-token doc summary is + `medium`, not `cheap`, even though the input is large. +- **Do not pass `model_config_id` in a `task()` call on mcode 0.2.4.** The + strict validator in `cli.js:B6c` rejects it; only `description` / `prompt` / + `agent_name` / `run_in_background` are allowed. Model selection is + session-level on 0.2.4. +- **Do not retry a failed sub-task on the same tier without re-classifying.** + A cheap-model failure on a synthesis-class task is a classification error; + the fix is to re-classify up, not to rephrase the brief. +- **Do not hide the tier from the user.** The tier is part of the contract — + they should be able to see "this is cheap because…" and disagree. + +## Verification checklist + +- [ ] Did you classify the sub-task into cheap / medium / main before spawning? +- [ ] Did you decide whether to spawn at all (the sub-agent gate)? +- [ ] Did you state the tier and the spawn reason in the brief? +- [ ] Did you avoid passing `model_config_id` in the `task()` call (not supported + on mcode 0.2.4)? +- [ ] If the sub-task failed, did you re-classify (not just rephrase)? +- [ ] If you ran a fan-out, did you record "X cheap / Y medium / Z main" in the + aggregation? diff --git a/plugins/antianqi/codex-harness-patterns/skills/parallel-fanout/SKILL.md b/plugins/antianqi/codex-harness-patterns/skills/parallel-fanout/SKILL.md new file mode 100644 index 0000000..af59ffc --- /dev/null +++ b/plugins/antianqi/codex-harness-patterns/skills/parallel-fanout/SKILL.md @@ -0,0 +1,167 @@ +--- +name: parallel-fanout +description: | + Decompose a task into 2+ truly independent sub-tasks and dispatch them concurrently via separate `task()` calls. Pick whether to fan out explicitly, not by accident. + USE WHEN: the user task is clearly decomposable into 2+ independent sub-tasks (independent files, independent probes, independent analyses), you would otherwise serialize work that has no real dependency, user said "in parallel" / "并行" / "fan out" / "spawn agents" / "同时跑". + TRIGGER PHRASES: "in parallel", "parallel", "fan out", "spawn agents", "并行", "同时", "concurrent", "subagents", "multi-agent", "同时跑几个". + SKIP WHEN: sub-tasks have a hard data dependency (output of A is input of B), the user explicitly said "sequential" / "one at a time", there is only one sub-task. +license: Apache-2.0 +compatibility: Targets MiniMax Code 0.2.4 `task` tool. Verified against the bundled `cli.js` schema. Each sub-task is a separate `task()` call with its own `description` / `prompt` / `agent_name`. `agent_name` is canonical (`explore` / `worker` / `verifier`); `mavis` is the root agent, not a sub-agent. +metadata: + author: antianqi + version: "1.2.0" + inspired-by: https://github.com/openai/codex/blob/main/codex-rs/core/src/thread_manager.rs (design principle); the fan-out decision and wait-for-all aggregation are portable; on mcode each sub-task is a discrete `task()` call + changes-from-v1.1.0: "Replaced `subagent_type=` with the canonical mcode `agent_name=`. Replaced `brief=` with `prompt=`. Dropped `mavis` from the sub-agent list (mavis is the root). Dropped the Codex-harness pseudocode block; mcode 0.2.4 is the only shape shown. The 'concurrency cap' step now references mcode's own per-session buffer-unordered limit instead of a hypothetical host config." +--- + +# Parallel Fanout + +When the user task is clearly decomposable into 2+ **truly independent** sub-tasks, the +agent has two choices: + +1. **Serialize**: do them one by one, holding the conversation hostage. +2. **Fan out**: dispatch them concurrently, aggregate the results. + +This Skill is about **knowing when to choose (2)** and **how to dispatch + aggregate +cleanly** so the user gets the parallel speedup without losing correctness. + +## mcode 0.2.4 surface + +Each sub-task is a separate `task()` call: + +```text +task( + description: string, // 3-5 word label, required + prompt: string, // the brief, required + agent_name: "explore" | "worker" | "verifier", // required + run_in_background?: boolean // optional; usually false for fan-out +) +``` + +The agent dispatches all the calls in a single response; mcode executes them +concurrently subject to the host's per-session buffer-unordered limit (8 by +default in 0.2.4; check the runtime config if unsure). The agent then waits +for all to complete before aggregating. + +`agent_name` is the canonical mcode spelling. `agent_name=` is accepted as +a runtime alias but the Skills prefer canonical. `mavis` is the root agent +not a sub-agent; do not pass it as `agent_name`. + +## When to use + +Activate when **any** of these is true: + +- The user task is clearly decomposable into 2+ independent sub-tasks. +- The sub-tasks touch **independent files / directories / systems** (so there is no + shared state to corrupt). +- The user explicitly said "in parallel" / "并行" / "fan out" / "同时". +- You would otherwise serialize work that has no real dependency. + +## When NOT to use + +- The sub-tasks have a **hard data dependency** (output of A is the input of B). +- The user explicitly said "sequential" / "one at a time" / "按顺序". +- There is only one sub-task (no fan-out to do). +- The sub-tasks would all touch the same file (race condition risk). + +## Process + +1. **Decompose explicitly**. Write the list of sub-tasks in the brief header before + dispatching anything. "Sub-tasks: A, B, C" is the single most important line. +2. **For each sub-task, decide context size** (see `fork-context-decision` Skill): + - Self-contained sub-task? `none` (just the brief in `prompt`). + - Needs prior context? `N` or `all` → inline the prior content into `prompt`. +3. **Check mcode's per-session buffer-unordered limit**. Default in 0.2.4 is 8 + concurrent `task` calls. If you have more sub-tasks, the host will queue or + fail — split the batch or use `run_in_background: true` and poll + `task_output` later. +4. **Dispatch the batch** in a single response. mcode runs them concurrently + subject to the buffer-unordered limit. +5. **Wait for all to complete**. The aggregator MUST verify each sub-task's + output before declaring success (use `completion-audit`). +6. **Surface the parallelism in the user-facing message**. "I dispatched 3 + sub-agents in parallel; here are their results." The user should know + fan-out actually happened (vs serial). + +## Output contract + +After activating this Skill, the agent's next message MUST include: + +- The **list of sub-tasks** dispatched (one per `task` call). +- The **chosen context level** per sub-task. +- The **aggregation** result (per-sub-task outcome + overall verdict). +- A **completion audit** step (each sub-task verified). + +## Common pitfalls + +- **Fanning out for the sake of it** — parallelism is a tool, not a goal. If two + sub-tasks are easier to do serially, do them serially. +- **Missing the data dependency** — the most common bug. Always check: does + sub-task B actually need sub-task A's output? If yes, serialize. +- **Hitting mcode's buffer-unordered limit silently** — the host will queue or + fail. Check the limit first; if you have more than 8, run them in waves. +- **Aggregating without verification** — one sub-task may have silently failed. + Always read each output. +- **Using `agent_name="mavis"`** — mavis is the root, not a sub-agent. + Use `explore` / `worker` / `verifier`. +- **Writing the sub-task brief in a separate `brief=` field** — mcode 0.2.4 + has no `brief` field. The brief goes in `prompt`. + +## Example + +The example below is **MiniMax Code 0.2.4 `task` tool syntax**. The fan-out +is 3 sub-tasks, all `none` context, all dispatched in one response, mcode +runs them concurrently. + +```text +# Sub-tasks: A, B, C +# Concurrency cap: 8 (mcode 0.2.4 default) +# Context level: none (all sub-tasks are self-contained) +# Aggregation: read each output, run completion-audit, then summarize + +> task( + description="Look up X in repo 1", + agent_name="explore", + prompt=""" + Task name: lookup-X-repo1 + Task: Find every file in that imports `X`. + Return: List of / files, one per line. + """ + ) + +> task( + description="Look up Y in repo 2", + agent_name="explore", + prompt=""" + Task name: lookup-Y-repo2 + Task: Find every file in that imports `Y`. + Return: List of / files, one per line. + """ + ) + +> task( + description="Look up Z in repo 3", + agent_name="explore", + prompt=""" + Task name: lookup-Z-repo3 + Task: Find every file in that imports `Z`. + Return: List of / files, one per line. + """ + ) + +# (Agent waits for all three.) +# Aggregator reads each output, audits per `completion-audit`. +``` + +The **decision** (3 sub-tasks, `none` context, wait-for-all) is the same; the +**call shape** is what mcode 0.2.4 actually exposes. + +## Verification checklist + +- [ ] Did you write the sub-task list in the brief header before dispatching? +- [ ] Did you stay under mcode's per-session buffer-unordered limit (default 8)? +- [ ] Did you choose the right context level per sub-task (via `fork-context-decision`)? +- [ ] Did you wait for all sub-tasks to complete before aggregating? +- [ ] Did you verify each sub-task's output (via `completion-audit`)? +- [ ] Did you use `agent_name` from `{explore, worker, verifier}` (not `mavis`)? +- [ ] Did you put the sub-task brief in the `prompt` field (not a separate `brief`)? diff --git a/plugins/antianqi/codex-harness-patterns/skills/plan-stream-emit/SKILL.md b/plugins/antianqi/codex-harness-patterns/skills/plan-stream-emit/SKILL.md new file mode 100644 index 0000000..6b48205 --- /dev/null +++ b/plugins/antianqi/codex-harness-patterns/skills/plan-stream-emit/SKILL.md @@ -0,0 +1,139 @@ +--- +name: plan-stream-emit +description: | + Before touching files on a non-trivial task, emit a structured plan and surface to the user for early course-correction. + USE WHEN: non-trivial task, multi-step task, ambiguous requirement, would take > 3 tool calls, user has not approved an approach yet, user said "plan first" / "before you start" / "let me see your approach" / "先出计划" / "出方案", crossing trust boundary (production, public repo, irreversible action). + TRIGGER PHRASES: "plan first", "先出计划", "let me see", "出方案", "确认一下", "先别动手", "想清楚再开始", "before you start", "我看看方案", "出 plan", "出计划". + SKIP WHEN: single one-shot question, user already gave numbered list of steps, trivially reversible, "do X" with X being one line. +license: Apache-2.0 +compatibility: Requires MiniMax Code with Agent Plugins 1.0 support. +metadata: + author: antianqi + version: "0.1.1" + inspired-by: https://github.com/openai/codex/blob/main/codex-rs/protocol/src/protocol.rs (PlanUpdate / PlanDelta) +--- + +# Plan Stream Emit + +For any non-trivial task, emit a structured plan first and let the user see it before you start +changing files. A plan is a list of small, ordered, named steps with explicit pass conditions — +not prose, not a single "I'll do X" line. + +## When to use + +Activate when **any** of these is true: + +- The user request is multi-step (more than 3 distinct actions). +- The task is ambiguous in any way: which file, which API, which framework, which version. +- The user request is large enough that getting it wrong would cost more than 2 minutes of + re-work. +- The user said "plan first" / "before you start" / "let me see your approach". +- The task crosses a trust boundary (production code, public repo, irreversible action). + +## When NOT to use + +- A single one-shot question or one-line edit. +- The user already gave a numbered list of steps ("do 1, 2, 3, 4 in order"). +- The task is trivially reversible and the cost of a wrong move is near zero. + +## Process + +1. **Stop and think before any tool call.** Do not start `bash`, `read`, or `write` until the + plan is on the page. +2. **Write the plan** as a `todowrite` list, in this exact shape: + + ```markdown + ## Plan — + + - [ ] **Step 1**: + Pass: + - [ ] **Step 2**: + Pass: + - [ ] **Step 3**: + Pass: + - [ ] **Step 4** (optional, only if needed): <...> + - [ ] **Step N** (always): Verify — + ``` + +3. **Add an "Open questions" section** if any step has un-resolved ambiguity: + + ```markdown + ## Open questions + + - + - + ``` + +4. **Surface the plan to the user** with a one-sentence preamble: "Here is my plan — I will + start with Step 1 once you confirm or correct it." Do not begin executing until the user + acks, **unless** the user has previously said "just go" or "no need to check in for this". +5. **Update the plan as you go.** When a step is done, mark it complete and emit the next + step's status. If reality diverges from the plan, **stop and re-plan** rather than silently + re-routing. +6. **Final step is always a Verify** — how the agent will confirm the whole task is done + (test pass, manual smoke, file existence check, etc.). + +## Output contract + +The user sees, in this order: + +- One-sentence preamble acknowledging the plan is coming. +- The `## Plan` block. +- (Optional) The `## Open questions` block. +- A clear stop point: "I'll start Step 1 once you confirm" (or, if pre-authorised, "Starting + Step 1."). + +After execution, the user sees the same plan with `[x]` checks updating live, and a final +Verify line that names what was actually checked. + +## Example + +```markdown +I'll plan the migration before touching files. + +## Plan — Migrate auth to OIDC alongside SAML + +- [ ] **Step 1**: Read src/auth/idp.rs and src/auth/callback.rs to map the current interface. + Pass: I can name every public function and its caller in one sentence. +- [ ] **Step 2**: Sketch the OidcProvider trait and one stub impl in a new file + src/auth/oidc/mod.rs. + Pass: `cargo check` passes with the new module imported. +- [ ] **Step 3**: Wire the new provider into the login/callback dispatch in src/auth/mod.rs, + gated on a config flag `auth.oidc_enabled`. + Pass: existing SAML tests still pass; `auth.oidc_enabled = false` is the default. +- [ ] **Step 4**: Add one happy-path test for OIDC login with a mock IdP. + Pass: `cargo test auth::oidc` is green. +- [ ] **Step 5**: Document the new config flag in docs/auth.md. + Pass: docs/auth.md lists `oidc_enabled`, `oidc_issuer`, `oidc_client_id`. +- [ ] **Verify**: Run the full test suite + a manual smoke against the dev OIDC sandbox. + +## Open questions + +- Do you have a preferred OIDC library (openidconnect crate vs oauth2 + manual JWKS)? +- Should the OIDC path share the session store with SAML, or own its own? +``` + +## Common pitfalls + +- **Do not emit a plan and then ignore it.** Every tool call should map to a step. If reality + diverges, **stop and re-plan**, do not silently re-route. +- **Do not write prose plans.** "I'll look at the code and then maybe refactor" is not a plan. + Every step names an action and a pass condition. +- **Do not skip the Verify step.** The user must be able to trust the agent to confirm the + whole task is done, not just the last file edit. +- **Do not over-plan trivial work.** A 3-line bug fix is one step. Save the structure for work + that needs it. +- **Do not surface a plan and immediately barrel into Step 1.** The user must have a chance to + redirect cheaply, before you have committed to a path. +- **Do not re-plan without telling the user.** "I am going to re-plan because Step 2 hit a + wall" is a one-line update; the user expects it. + +## Verification checklist + +- [ ] Did the plan come before any tool call? +- [ ] Is every step a verb-first sentence with a pass condition? +- [ ] Did you include a final Verify step? +- [ ] Did you list Open questions instead of guessing on ambiguity? +- [ ] Did the user have a chance to confirm or redirect? +- [ ] Did you update the plan as steps completed, rather than drifting silently? +- [ ] At the end, did the Verify step actually run and pass? diff --git a/plugins/antianqi/codex-harness-patterns/skills/plugin-author-helper/SKILL.md b/plugins/antianqi/codex-harness-patterns/skills/plugin-author-helper/SKILL.md new file mode 100644 index 0000000..f51a7c7 --- /dev/null +++ b/plugins/antianqi/codex-harness-patterns/skills/plugin-author-helper/SKILL.md @@ -0,0 +1,227 @@ +--- +name: plugin-author-helper +description: | + Design, validate, and ship a marketplace Plugin (or Skill bundle) with proper manifest format, multi-ecosystem compatibility, version pinning, manifest fallback, install idempotency, and three-layer startup sync. + USE WHEN: writing a new Plugin manifest, picking manifest format (Legacy vs AgentPlugin), adding `skills` / `mcp_servers` / `apps` / `hooks` / `interface` fields, validating a plugin before publish, designing marketplace install/remove/upgrade flows, or any task involving "make my plugin actually work in Codex". + TRIGGER PHRASES: "plugin manifest", "PluginManifest", "marketplace", "agent plugin", ".agents/plugins/marketplace.json", "manifest fallback", "idempotency key", "plugin author", "plugin publish", "plugin version", "startup sync", "lock file". + SKIP WHEN: writing a single skill (use `skill-auto-select`), pure MCP server (use `mcp-server` directly), one-off tool without packaging. +license: Apache-2.0 +compatibility: Requires MiniMax Code with Agent Plugins 1.0 support. +metadata: + author: antianqi + version: "0.1.0" + inspired-by: https://github.com/openai/codex/tree/main/codex-rs/core-plugins/ (P-93/94/95/99) + changes-from-v0.0.0: "Initial design distilled from P-93/94/95/99 deep-dive (Phase 1 Week 4)." +--- + +# Plugin Author Helper + +Design, validate, and ship a Plugin that fits into the Codex (or compatible) Plugin +ecosystem. Mirrors the design of `codex-rs/core-plugins/`. + +## When to use + +Activate when: + +- Writing a new `plugin.json` / `marketplace.json` manifest. +- Choosing between manifest formats (Legacy vs AgentPlugin). +- Validating a plugin before publish. +- Designing install / remove / upgrade / sync flows. +- Picking a plugin scope (User / System / Admin / Plugin) and pinning a version. + +## When NOT to use + +- Single skill authoring → use `skill-auto-select`. +- Pure MCP server (no Skill bundle) → use the `mcp-server` crate's own conventions. +- One-off tool scripts → don't package. + +## Host runtime requirements + +This Skill describes **how to design** a Plugin (manifest, idempotency, scope). +It does **not** cause the agent to install, modify, or publish anything on its own. +Specifically, the agent MUST NOT, on the strength of this Skill alone: + +- Run `npm install` / `npm link` / any package manager command for the user. +- Write or overwrite files in `~/.minimax/.../plugins/`, `~/.codex/.../`, + `~/.config/`, or any other user-level config directory. +- Hit a marketplace endpoint (download, install, upgrade) on the user's behalf. +- Trigger a plugin sync that reaches the network (3-layer fallback in Codex is a + Codex-runtime concept; MiniMax Code may or may not have an equivalent). + +All of the above require **explicit user confirmation** in the host's normal +permission flow (`approval_policy`, `ask` mode, or whatever the host uses). +This Skill is for **designing** the manifest / sync flow, not for **executing** it. +The agent that *runs* the install / sync must follow the host's user-confirmation +policy, **not** the patterns in this Skill. + +## Process + +### 1. Pick the manifest format + +Codex supports two manifest formats: + +| Format | Path | Notes | +|---|---|---| +| `Legacy` | `.claude-plugin/marketplace.json` / `.cursor-plugin/marketplace.json` | Older ecosystems. | +| `AgentPlugin` | `.agents/plugins/marketplace.json` / `.agents/plugins/api_marketplace.json` | Current Codex format. | + +If the plugin should be cross-ecosystem (OpenAI + Claude + Cursor), ship both manifests and let the loader pick whichever it finds first. + +### 2. Write the 8-field PluginManifest + +```rust +struct RawPluginManifest { + name: String, // required + version: Option, // semver recommended + description: Option, // one-line + keywords: Vec, // tags + skills: Option, // "./skills//SKILL.md" (./... required) + mcp_servers: Option, // MCP config + apps: Option, // apps connector + hooks: Option, // 9 hook trigger points + interface: Option, // UI (display_name, icon, brand_color) +} +``` + +**Hard limits**: + +- `MAX_DEFAULT_PROMPT_COUNT: 3` — at most 3 default prompts in `interface`. +- `MAX_DEFAULT_PROMPT_LEN: 128` — each prompt ≤ 128 chars. +- All paths in `skills` MUST use the `./...` syntax (`./skills//SKILL.md`) and resolve under the plugin root. + +### 3. Provide a manifest fallback + +If the main manifest is missing or malformed, fall back to a known-good shape. The +fallback typically contains just `name` + `version` + a minimal `skills` list. + +### 4. Use a 3-letter marketplace name taxonomy + +Pick a short, descriptive name with one of these prefixes: + +| Prefix | Meaning | +|---|---| +| `openai-curated` | OpenAI-curated official | +| `openai-api-curated` | OpenAI API curated | +| `openai-bundled` | Bundled with Codex | +| `openai-bundled-alpha` | Bundled alpha | +| `openai-primary-runtime` | Primary runtime | + +For your own marketplace, use `-` (e.g. `acme-data-pipelines`). + +### 5. Use idempotency keys for create operations + +```rust +pub struct CreateProjectParams { + pub name: String, + pub idempotency_key: String, // ← critical + // ... +} + +pub struct CreatedProject { + pub project: StoredProject, + pub created: bool, // true = new, false = idempotent hit +} +``` + +**Always require `idempotency_key`** on create / install endpoints. The same key + same payload returns the existing object with `created: false`. Different key + same name creates a new object (no conflict). + +### 6. Three-state updates: `Option>` + +For partial-update APIs: + +- `None` — "do not touch this field". +- `Some(None)` — "set this field to null/empty". +- `Some(Some(value))` — "set this field to value". + +This is the only correct encoding for "no change vs explicit clear" in JSON. + +### 7. Report moved vs unchanged + +```rust +pub enum ProjectMoveOutcome { Moved, Unchanged } +``` + +Reorder APIs should return whether the operation actually moved anything. UI uses this to skip re-renders on no-ops. + +### 8. Use a `BTreeMap` for metadata + +Stable iteration order = stable output. Don't use `HashMap` for user-visible metadata. + +### 9. Three-layer startup sync + +When the marketplace needs to refresh plugins at every Codex startup, use this 3-layer fallback: + +```text +1) GitHub API → GET /repos/openai/plugins/git/refs/codex/curated-sync + compare SHA against .tmp/plugins.sha + if changed, download + extract +2) Backend archive fallback → GET /backend-api/plugins/export/curated +3) Git clone → git clone https://github.com/openai/plugins.git --branch refs/codex/curated-sync +``` + +Each layer has a 30s timeout. Use a lock file (`.tmp/plugins.sync.lock`) to prevent +concurrent syncs from multiple Codex processes. Use a SHA cache (`.tmp/plugins.sha`) +to skip work when nothing changed. Stale temp dirs (older than 10 min) are auto-cleaned. + +### 10. Decide a scope per skill within the plugin + +Each skill in your plugin should be `User` (user-installed) / `System` (bundled) / `Plugin` (this plugin) scoped. Document the scope in the frontmatter `metadata.scope` field. + +## Output contract + +A plugin that follows this design: + +- Has both `plugin.json` (AgentPlugin) AND a fallback manifest. +- Has `name` / `version` / `description` / `keywords` / `skills` / `mcp_servers` / `apps` / `hooks` / `interface` set. +- Default prompts ≤ 3 entries, each ≤ 128 chars. +- All skill paths use the `./...` syntax. +- Has a marketplace name following the prefix taxonomy. +- All create / install endpoints require an `idempotency_key`. +- Uses `BTreeMap` for any user-visible metadata. +- If a startup sync is needed, it uses a 3-layer fallback with a lock file and SHA cache. + +## Common pitfalls + +- **No idempotency key** → user retries after a network blip create duplicates. Always require it. +- **`HashMap` for metadata** → JSON output flickers on every render. Use `BTreeMap`. +- **Two syncs in parallel** → file corruption. Lock file mandatory. +- **Forgetting fallback layer** → GitHub outage takes down all installs. Always have the archive + clone as backups. +- **Default prompts > 3** or > 128 chars → silently truncated. Stay under the limit. +- **Skill paths not starting with `./`** → resolution fails. Always use `./skills/.../SKILL.md`. +- **`Option` instead of `Option>`** → cannot distinguish "no change" from "set to null". + +## Example — minimal plugin manifest + +```json +{ + "$schema": "https://agent-plugins.org/schemas/1.0/plugin.schema.json", + "name": "acme-data-pipelines", + "version": "0.1.0", + "description": "Data pipeline skills for ETL, schema validation, and warehouse sync.", + "keywords": ["data", "etl", "pipeline"], + "skills": "./skills/*/SKILL.md", + "mcp_servers": { + "warehouse": { + "transport": "stdio", + "command": "./bin/warehouse-mcp" + } + }, + "interface": { + "display_name": "ACME Data Pipelines", + "brand_color": "#0066cc" + } +} +``` + +## Verification checklist + +- [ ] Manifest has all 8 fields; `name` is set. +- [ ] Default prompts ≤ 3 entries, each ≤ 128 chars. +- [ ] All skill paths use the `./...` syntax. +- [ ] Manifest fallback present. +- [ ] Marketplace name follows the prefix taxonomy. +- [ ] All create / install endpoints require an `idempotency_key`. +- [ ] Partial updates use `Option>` for 3-state. +- [ ] Reorder APIs return `Moved` / `Unchanged`. +- [ ] User-visible metadata uses `BTreeMap` not `HashMap`. +- [ ] If startup sync is used: 3-layer fallback, lock file, SHA cache, 30s timeout, 10-min stale cleanup. diff --git a/plugins/antianqi/codex-harness-patterns/skills/retry-with-backoff/SKILL.md b/plugins/antianqi/codex-harness-patterns/skills/retry-with-backoff/SKILL.md new file mode 100644 index 0000000..1d4d6db --- /dev/null +++ b/plugins/antianqi/codex-harness-patterns/skills/retry-with-backoff/SKILL.md @@ -0,0 +1,159 @@ +--- +name: retry-with-backoff +description: | + Execute explicit retry policy: max 3, base 2s, max 30s, full jitter, 60s total budget, respects `Retry-After`. + USE WHEN: `error-recovery-strategy` classified error as `transient` and chose `retry`, HTTP 429 with `Retry-After` header, network timeout/refused/reset, queue/lock/eventually-consistent read returned stale, user said "重试" / "retry" / "再试" / "等一下" / "等几秒" / "backoff" / "exponential" / "rate limit" / "429" / "限流". + TRIGGER PHRASES: "重试", "retry", "再试", "等一下", "等几秒", "backoff", "exponential", "rate limit", "429", "Retry-After", "throttled", "限流", "busy", "服务忙". + SKIP WHEN: error is `deterministic` (won't change on retry), error is `unknown` (escalate to ask-user), work is time-sensitive and 30s backoff is too late. +license: Apache-2.0 +compatibility: Requires MiniMax Code with Agent Plugins 1.0 support. +metadata: + author: antianqi + version: "0.1.1" + inspired-by: https://github.com/openai/codex/blob/main/codex-rs/code-mode/src/grpc_session/reconnect.rs +--- + +# Retry With Backoff + +Retry is the most common recovery action — and the easiest to do badly. Retrying +without a budget burns wall-clock and budget. Retrying with too-aggressive a backoff +hits the rate-limited service again. Retrying without jitter causes a thundering herd +when many agents retry the same service at the same instant. + +This Skill defines a single, explicit retry policy. **Always state the policy before +running the retries.** Do not improvise. + +## When to use + +Activate when **any** of these is true: + +- `error-recovery-strategy` Skill categorised the error as `transient` and chose + `retry` as the action. +- A rate-limited service (HTTP 429, API quota) returned a `Retry-After` header. +- A network call timed out, refused, or reset. +- A queue / lock / eventually-consistent read returned a stale or empty result. + +## When NOT to use + +- The error is `deterministic` (permission denied, file not found). Retry will fail + the same way. Switch tool or ask user instead. +- The error is `unknown`. Retry without a clear reason is gambling. Ask user. +- The work is time-sensitive enough that a 30-second backoff would be too late. In + that case, retry **without** backoff (1 immediate attempt) and then escalate to + user on failure. + +## Process + +1. **Pick the policy before retrying.** Defaults (override only with reason): + + | Parameter | Default | Why | + |---|---|---| + | `max_attempts` | `3` (original + 2 retries) | Three strikes; the third strike is the cost of "I'm sure it's transient." | + | `base_delay_seconds` | `2` | Short enough to be useful, long enough to feel the first retry. | + | `max_delay_seconds` | `30` | Above 30 s the user has usually already given up mentally. | + | `total_time_budget_seconds` | `60` | Hard ceiling; beyond this, escalate to user. | + | `jitter_strategy` | `full` (uniform 0..delay) | Prevents thundering herd. | + | `respect_retry_after` | `true` (if server provides `Retry-After`, use it) | Server knows more than we do. | + +2. **State the policy in the response, in one line:** + + ```text + Retry plan: 3 attempts, base 2s, max 30s, full jitter, budget 60s total + ``` + +3. **Compute each retry's actual delay as:** + + ```text + delay(n) = min(base * 2^(n-1), max_delay) + actual_delay(n) = uniform(0, delay(n)) # full jitter + ``` + + For `n = 1, 2, 3` with base 2s and max 30s: + - attempt 1: `min(2, 30) = 2s`, jittered to `[0, 2]`s + - attempt 2: `min(4, 30) = 4s`, jittered to `[0, 4]`s + - attempt 3: `min(8, 30) = 8s`, jittered to `[0, 8]`s + +4. **If the server returned a `Retry-After` header**, use `max(delay(n), retry_after)` — + the server is saying "wait at least this long," not "wait exactly this long." + +5. **Between attempts**, do not start any other tool call. The whole point of the + delay is to let the service recover. If you are tempted to "do other work while + waiting," surface to the user instead — they may want to know you are in retry + mode before you start something else. + +6. **After the last attempt**, evaluate: + - **Success** → continue, mention the retry in the result so the user knows it + took longer than expected. + - **Still failing** → escalate to `error-recovery-strategy`'s `ask-user` action. + Do not silently extend the retry count. Do not skip-with-warning. + +7. **If the total time budget (60s default) is exceeded mid-retry**, stop the next + delay and escalate immediately. Time budget is **hard**, not soft. + +## Output contract + +The user sees, in this order: + +- One-line retry plan (max attempts, base, max, jitter, total budget). +- (Per attempt) one line: "attempt N failed: ; waiting Ms." +- After the last attempt: success → continue, failure → escalate to `ask-user`. +- A short post-mortem: "Why did this take N×t? The server was rate-limiting / network + was congested / etc." if you can identify the cause; "unknown" is acceptable. + +## Example + +```text +Retry plan: 3 attempts, base 2s, max 30s, full jitter, budget 60s total + +attempt 1: GET /api/v1/users/123 → 503 Service Unavailable + waiting 1.4s (jittered from 2s) + +attempt 2: GET /api/v1/users/123 → 503 Service Unavailable + waiting 3.1s (jittered from 4s) + +attempt 3: GET /api/v1/users/123 → 200 OK + total time: 4.5s (well under 60s budget) +``` + +Counter-example (escalate, do not extend): + +```text +Retry plan: 3 attempts, base 2s, max 30s, full jitter, budget 60s total + +attempt 1: ... 503 +attempt 2: ... 503 +attempt 3: ... 503 + total time: 4.5s + +Recovery decision: ask-user +Reason: 3 attempts exhausted within budget; error is consistently 503; switching + to ask rather than extending to attempt 4. +``` + +## Common pitfalls + +- **Do not retry without a budget.** Even one undeclared retry can burn 5 minutes. +- **Do not skip jitter.** A "deterministic" delay causes thundering herd when multiple + agents retry the same service. Always jitter. +- **Do not retry past the total time budget.** The budget is hard. If you exceed it, + you have failed the Skill, regardless of how many more attempts you could fit. +- **Do not retry `deterministic` errors.** A permission-denied error will fail every + time. Switch or ask. +- **Do not start other work between retries.** The delay is for the service, not for + you. If the user has new instructions, queue them or surface a question. +- **Do not respect `Retry-After` blindly.** It is a *minimum*, not a *maximum*. Use + `max(delay(n), retry_after)`, not `retry_after` alone. +- **Do not silently extend.** If 3 attempts failed, escalate. Do not try 4 "just + in case." The user should know the work is blocked. + +## Verification checklist + +- [ ] Did you state the retry plan (max attempts, base, max, jitter, budget) before + retrying? +- [ ] Did you respect any `Retry-After` from the server (using `max`)? +- [ ] Did you add full jitter to every delay? +- [ ] Did you cap each delay at the configured `max_delay_seconds`? +- [ ] Did you stop at `max_attempts` (no silent extension)? +- [ ] Did you stop at `total_time_budget_seconds` (no overrun)? +- [ ] On exhaustion, did you escalate to `ask-user` (not silent skip)? +- [ ] Did you report the total time and cause (if known) in the post-mortem? diff --git a/plugins/antianqi/codex-harness-patterns/skills/review-mode/SKILL.md b/plugins/antianqi/codex-harness-patterns/skills/review-mode/SKILL.md new file mode 100644 index 0000000..b8f775d --- /dev/null +++ b/plugins/antianqi/codex-harness-patterns/skills/review-mode/SKILL.md @@ -0,0 +1,147 @@ +--- +name: review-mode +description: | + Switch to critic mode after finishing a chunk, produce PASS / FIX / REDO verdict. + USE WHEN: sub-task boundary reached, user said "review" / "double-check" / "is this right" / "spot the bug" / "看一下" / "review 一下", before reporting "done" on anything user will rely on, after writing code / config / doc, after sub-agent returns. + TRIGGER PHRASES: "review", "double-check", "看一下", "review 一下", "查一下", "检查", "找 bug", "is this right", "spot the bug", "verifier", "自己 review 一下". + SKIP WHEN: one-line edit, user explicitly said "ship it" / "no more review" / "不用 review" in this turn, user can see result in chat immediately. +license: Apache-2.0 +compatibility: Requires MiniMax Code with Agent Plugins 1.0 support. +metadata: + author: antianqi + version: "0.1.1" + inspired-by: https://github.com/openai/codex/blob/main/codex-rs/protocol/src/protocol.rs (EnteredReviewModeEvent / ExitedReviewModeEvent) +--- + +# Review Mode + +After you write code, before you say "done", switch hats. You were the author; now you are the +reviewer. The reviewer has one job: find the things the author didn't. + +This Skill is the cheapest insurance you can buy against the most common agent failure mode — +declaring a task done when it is not. + +## When to use + +Activate when **any** of these is true: + +- A non-trivial sub-task has just finished (a function, a file, a config, a migration, a test + suite, a doc section). +- The user said "review this", "double-check", "is this right", "spot the bug", or "any issues + with this?". +- You are about to mark a `todowrite` step `[x]` as done and that step touches anything the + user will rely on. +- The work crossed a trust boundary (production, public API, schema, persisted data, a contract + someone else will code against). + +## When NOT to use + +- A trivial one-line edit. The marginal value of review is too low to pay the token cost. +- The user explicitly said "ship it" / "no review" / "just commit" in this turn. +- You are mid-stream on a single step and the next step will surface errors anyway (e.g. + running the test suite next). +- The work is exploratory (a sketch, a draft, "show me what you mean"). Review it later, when + the draft becomes a proposal. + +## Process + +1. **State the review scope** in one sentence before you read anything: "Reviewing the auth + refactor: 4 files changed, new `OidcProvider` trait, SAML tests must still pass." Future you + needs the boundary. +2. **Re-read your own output from a critic's position.** Open the file(s) you changed. Do not + re-read the diff; read the **result**. Look for: + - Off-by-one, wrong-sign, null/None mishandling, empty-collection edge cases. + - Naming that lies (function called `validate` that does not validate). + - Log/error paths that swallow useful information. + - Tests that pass for the wrong reason (e.g. asserting `==` on a value the function never + returns). + - Public API surface that locks in a bad design (a struct that is too wide to evolve, a + flag that should be an enum). + - Comments that contradict the code. +3. **Run the verifier if there is one.** Tests, linter, type-check, schema diff, manual smoke. + If the verifier says green, you have *evidence*; if it is silent, you have *hope*. Do not + ship hope. +4. **Produce a verdict** in this exact shape: + + ```markdown + ## Review — + + **Verdict**: PASS / PASS with caveats / FIX required / REDO + + **What I checked** (bullet list of specific things): + - <...> + + **What I found** (concrete defects, not vibes): + - + - (or "none") + + **What I am unsure about** (so the user can decide): + - <...> + - (or "nothing — the verifier ran and the design matches the spec") + ``` + +5. **Apply fixes if the verdict is FIX / REDO and the fix is small.** Do not fix large things + in review mode; surface them and start a new `plan-stream-emit` cycle. +6. **If PASS**, continue to the next step. The review record is part of the audit trail — + keep it short but specific. + +## Output contract + +The user sees, in this order: + +- One-line scope statement. +- The Review block above. +- (If fix) the one-line summary of what you fixed. +- (If PASS) the next concrete step. + +## Example + +```markdown +Reviewing the auth refactor: 4 files changed, new `OidcProvider` trait, SAML tests must still pass. + +## Review — auth refactor (OIDC adapter v1) + +**Verdict**: PASS with caveats + +**What I checked**: +- `cargo check` on the workspace +- `cargo test auth::` (all 14 tests pass, including the 12 unchanged SAML ones) +- the new `OidcProvider` trait signature for type-correctness +- the `auth.oidc_enabled = false` default path against the existing SAML flow + +**What I found**: +- `src/auth/oidc/mod.rs:42` — `expires_at` is `i64` not `u64`; future-dated tokens underflow. + Fix applied (cast + `saturating_sub`). +- `src/auth/callback.rs:91` — error path on token exchange returns the raw HTTP body, leaks + the client_secret on 4xx. Fix applied (redact before returning). +- nothing else + +**What I am unsure about**: +- whether the OIDC `nonce` claim should be persisted in the session store; the Okta + spec says yes, Auth0 says optional. Pick before merging. +``` + +## Common pitfalls + +- **Do not review your own diff — review the result.** A diff makes you forgive yourself + (you remember why each line is there). The file on disk has no such forgiveness. +- **Do not write "looks good" as a verdict.** "Looks good" is a vibe, not a finding. Name the + specific things you checked, even if they are negative ("verified X, Y, Z are absent"). +- **Do not skip the verifier step.** If there is no test, run the build. If there is no build, + read the file with a critical eye. +- **Do not fix in review mode beyond trivial.** Anything that takes more than 2-3 minutes to + fix is a new sub-task, not a review item. Surface it. +- **Do not produce a 50-line review report for a 5-line change.** Match the report size to + the change size. +- **Do not review work you did not just do.** If the user asks you to review code from last + week, this Skill's "just finished" assumption does not hold — re-anchor by stating scope. + +## Verification checklist + +- [ ] Did you state the review scope in one sentence? +- [ ] Did you re-read the file(s) from a critic's position, not the diff? +- [ ] Did you run the verifier (tests / lint / type-check / smoke) and cite its result? +- [ ] Is the verdict one of {PASS, PASS with caveats, FIX required, REDO}? +- [ ] Is "What I found" specific (file:line — defect — fix), not vague? +- [ ] If you applied a fix, was it trivial (< 2-3 minutes)? +- [ ] Did the user see the review record before you moved on? diff --git a/plugins/antianqi/codex-harness-patterns/skills/session-branch-fork/SKILL.md b/plugins/antianqi/codex-harness-patterns/skills/session-branch-fork/SKILL.md new file mode 100644 index 0000000..e0d607e --- /dev/null +++ b/plugins/antianqi/codex-harness-patterns/skills/session-branch-fork/SKILL.md @@ -0,0 +1,272 @@ +--- +name: session-branch-fork +description: | + Design a session-level fork / revert / recover / suspend mechanism over a paginated history with lineage tracking, immutable segments, global lock, ModelContext reconstruction, and bounded replay. + USE WHEN: designing session persistence, building a "fork this conversation" feature, building "undo last N turns", building "suspend and resume later", implementing a paginated history with segment-level cursor, reconstructing ModelContext from disk, or any task involving "session as a git-like object graph". + TRIGGER PHRASES: "session fork", "session branch", "thread fork", "thread rollback", "revert thread", "ThreadRollback", "SuspendTurnAndShutdown", "Op::RecoverTurn", "paginated history", "RolloutLineage", "ForkBoundary", "RolloutReferenceIndex", "ModelContext reconstruction", "ReverseJsonlScanner", "bounded replay", "git baseline", "writer lock", "subagent lineage". + SKIP WHEN: single-session task state (use `world-state-tracking`), no need to undo, no need to fork. +license: Apache-2.0 +compatibility: Requires MiniMax Code with Agent Plugins 1.0 support. +metadata: + author: antianqi + version: "0.1.0" + inspired-by: https://github.com/openai/codex/tree/main/codex-rs/thread-store/ (P-49/50/51/52 + P-67-77) + changes-from-v0.0.0: "Initial design distilled from P-49/50/51/52 + P-67-77 deep-dive (Phase 0 错判修正 + Phase 1 Week 3)." +--- + +# Session Branch / Fork + +Design a session-level fork / revert / recover / suspend system over a paginated +history. Mirrors `codex-rs/thread-store/`. + +## When to use + +Activate when designing: + +- A "fork this session from turn N" feature. +- A "undo the last N turns" feature. +- A "suspend the running session, recover it later" feature. +- A paginated history with cross-segment cursors. +- A model-context reconstruction algorithm that doesn't re-read the entire history. + +## When NOT to use + +- Single-session task state → use `world-state-tracking`. +- No undo, no fork, no suspend needed → standard append-only history is enough. + +## Process + +### 1. Pick a `ThreadHistoryMode` + +```rust +pub enum ThreadHistoryMode { + Legacy, // entire thread in one JSONL + Paginated, // immutable segments, supports fork/revert +} +``` + +**Default to Legacy** for backward compatibility, but use Paginated for any new +thread. Paginated is what makes fork / revert / lineage work. + +### 2. Define the immutable segment model + +```rust +pub struct RolloutLineageSegment { + pub rollout_id: ThreadId, + pub rollout_path: PathBuf, + pub start_ordinal: u64, + pub end: Option, // byte offset +} + +pub struct RolloutLineage { + pub segments: Vec, +} +``` + +**Key invariants**: + +- Segments are **immutable**. A new segment is created for any change. +- The lineage is a list of segments ordered from oldest to newest. +- Each segment knows its start ordinal and (optionally) its end offset. + +### 3. Implement fork with `ForkBoundary` + +```rust +pub enum ForkBoundary { + Latest, // inherit source's latest durable state + ThroughTurn(String), // include this turn + BeforeTurn(String), // exclude this turn +} + +pub struct PrepareForkParams { + pub thread_id: ThreadId, + pub boundary: ForkBoundary, +} + +pub struct PreparedFork { + pub source_thread_id: ThreadId, + pub model_context: Arc, +} +``` + +Fork flow: + +1. Lock the source thread's lifecycle + writer. +2. Persist any pending items in the source. +3. Resolve the source's `RolloutLineage`. +4. Materialize each ancestor segment to SQLite (if not already). +5. Load the `ModelContext` for the chosen boundary. +6. Return a `PreparedFork` ready to be turned into a new thread. + +### 4. Implement revert with CAS + +```rust +pub struct RevertThreadParams { + pub thread_id: ThreadId, + pub before_turn_id: String, // first turn EXCLUDED from retained history +} +``` + +Revert flow: + +1. Lock lifecycle + writer + writer_lock_coordinator. +2. Resolve the current rollout from SQLite (`expected_sqlite_path` is the CAS anchor). +3. Read `SessionMeta` from the current rollout, verify `id == thread_id` and `history_mode == Paginated`. +4. Materialize any compressed lineage segments. +5. Create a new immutable rollout file referencing the retained prefix. +6. CAS the SQLite `rollout_path` to the new file. + +**Critical contract**: `revert` only rolls back in-memory context. **It does not undo filesystem changes.** The client is responsible for undoing edits on disk. + +### 5. Implement suspend + recover + +```rust +Op::SuspendTurnAndShutdown { reply: oneshot::Sender<...> } +Op::RecoverTurn { thread_settings, reply: oneshot::Sender<...> } +``` + +Suspend flow (9 steps): + +1. Lock `active_turn` and verify `task.kind == TaskKind::Regular`. +2. Snapshot descendants (not a seal; best-effort). +3. `live_thread.flush()` — persistence first; if it fails, leave the turn running. +4. Re-lock and re-verify kind (flush can yield). +5. Take the turn and task; cancel the cancellation token; cancel git enrichment. +6. `task.handle.detach()` with a `GRACEFULL_INTERRUPTION_TIMEOUT_MS` timeout. +7. `session.input_queue.clear_pending(&turn)` — pending input is NOT persisted. +8. `shutdown_session_runtime(session)` + `live_thread.flush()` + `live_thread.shutdown()`. +9. Emit `ShutdownComplete` event **only after** the writer is closed. + +**Critical contract**: do NOT record a terminal turn event on suspend. This +intentionally leaves the turn's ID reclaimable, so `Op::RecoverTurn` can resume it. + +### 6. Use a global lock for multi-process safety + +When two Codexes might fork / revert the same thread, use a DB-level lease: + +- `try_claim_global_phase2_job(thread_id, JOB_LEASE_SECONDS)` — single-writer. +- `heartbeat_global_phase2_job(ownership_token, JOB_LEASE_SECONDS)` — keep lease alive. +- On agent completion, re-verify ownership BEFORE `reset_git_repository` to avoid + resetting someone else's work. + +### 7. Reconstruct ModelContext with bounded replay + +```rust +pub fn load_latest_model_context(store, params) -> StoredModelContext { + let path = thread_rollout_resolver::resolve_current(...).await?; + let session_meta = read_session_meta_line(path).await?; + if session_meta.id != params.thread_id { return Err(InvalidRequest); } + + let mut scanner = ReverseJsonlScanner::new(file)? + .with_max_record_bytes(MAX_ROLLOUT_LINE_BYTES); + let mut scan = ModelContextScan::default(); + + while let Some(outcome) = scanner.scan_next::()? { + if let ScanOutcome::Parsed(value) = outcome { + if scan.push(line) == ModelContextScanProgress::Complete { + let items = scan.finish(session_meta); + items.retain(|i| !matches!(i, RolloutItem::SessionMeta(_))); + return Ok(StoredModelContext { items }); + } + } + // ScanOutcome::Rejected — skip bad line, do not abort + } + // No bounded cutoff found — fall back to full replay +} +``` + +Three guarantees: + +- `ReverseJsonlScanner` reads from the END of the file — only the suffix is touched. +- `MAX_ROLLOUT_LINE_BYTES` prevents a single bad line from OOM-ing the reader. +- `ScanOutcome::Rejected` skips malformed lines; never aborts the whole read. +- If no bounded cutoff is found, fall back to the full replay (read entire file). + +### 8. Run a Legacy → Paginated migration on startup + +```rust +pub async fn migrate_rollouts_on_startup(store) { + // Use a creation-ordered cursor in SQLite to check only newer rollout files + // 48h lookback window catches files we skipped earlier + // Fingerprint (size_bytes + modified_at_ns) skips empty/malformed rollouts + // Use run marker to prevent overlapping migrations + // Spawn subagent-aware bounded replay for subagent rollouts (don't copy + // the parent's full history into every child) +} +``` + +### 9. Use a writer lock for serialization + +`writer_lock_coordinator.acquire(thread_id)?` — guarantees that writes to a +single thread are serialized even across processes. Drop the lock when the +thread is closed. + +### 10. Adopt the 3-state section / project model + +```rust +pub struct UpdateProjectParams { + pub project_id: String, + pub name: Option, // None = no change + pub roots: Option>, + pub metadata: Option>, +} +``` + +Use `Option>` for 3-state: `None` = no change, `Some(None)` = clear, +`Some(Some(v))` = set. + +## Output contract + +A session-management system that follows this design: + +- Threads have a `ThreadHistoryMode` (Legacy / Paginated). +- Paginated threads have a `RolloutLineage` of immutable segments. +- Fork accepts a `ForkBoundary` and returns a `PreparedFork`. +- Revert uses CAS on the SQLite rollout_path; creates a new immutable segment. +- Suspend + Recover are paired; suspend does NOT record a terminal event. +- Reconstruct ModelContext via reverse scan with bounded replay + byte cap + Rejected skip. +- Migration runs at startup with cursor in DB + fingerprint skip + 48h lookback. +- Multi-process safety via DB leases and writer lock coordinator. + +## Common pitfalls + +- **No `Paginated` history mode** → no fork / revert. New threads should default to Paginated. +- **Revert undoes filesystem** → it does not. Client is responsible. +- **Suspend records terminal event** → Recover can never resume. Don't. +- **Pending input persisted** → replay state is wrong. `clear_pending` on suspend. +- **Reverse scan reads whole file** → slow. Bounded replay + `MAX_ROLLOUT_LINE_BYTES`. +- **Migration on every startup reads all rollouts** → slow. Cursor + 48h lookback. +- **Two Codexes reverting simultaneously** → corruption. Lease / writer lock. +- **Reset git baseline with diff present** → deleted content stays in git objects. Delete diff first. +- **Single-type partial update vs. clear** → use `Option>` for 3-state. + +## Example — fork from a turn + +```text +# Source thread has 50 turns +fork_boundary = BeforeTurn("turn-30") +prepare_fork(thread_id, boundary) → + 1. Lock source lifecycle + writer + 2. Persist pending items + 3. Resolve lineage (5 segments) + 4. Materialize segments 1..4 to SQLite + 5. ModelContext = base + turns 1..29 (before turn-30) + 6. PreparedFork { source_thread_id, model_context } +new_thread = create_thread(forked_from_id = source.id, history_base = ModelContext.snapshot) +# New thread starts at turn 30, inherits turns 1..29 from ModelContext +``` + +## Verification checklist + +- [ ] All new threads default to `ThreadHistoryMode::Paginated`. +- [ ] Fork accepts `ForkBoundary` (Latest / ThroughTurn / BeforeTurn). +- [ ] Revert uses CAS on the SQLite rollout path; creates a new immutable segment. +- [ ] Suspend flushes BEFORE canceling, and re-checks turn kind after flush. +- [ ] Suspend does NOT record a terminal turn event. +- [ ] Suspend clears pending input (input is not persisted). +- [ ] Suspend emits `ShutdownComplete` ONLY after `live_thread.shutdown()` returns. +- [ ] Reconstruct ModelContext via `ReverseJsonlScanner` with byte cap. +- [ ] Reconstruct skips `ScanOutcome::Rejected` without aborting. +- [ ] Migration uses SQLite cursor + fingerprint skip + 48h lookback. +- [ ] Multi-process safety: writer lock + DB lease + ownership token. +- [ ] Partial-update APIs use `Option>` for 3-state. diff --git a/plugins/antianqi/codex-harness-patterns/skills/session-handoff/SKILL.md b/plugins/antianqi/codex-harness-patterns/skills/session-handoff/SKILL.md new file mode 100644 index 0000000..2e28eab --- /dev/null +++ b/plugins/antianqi/codex-harness-patterns/skills/session-handoff/SKILL.md @@ -0,0 +1,207 @@ +--- +name: session-handoff +description: | + At session end, write a structured handoff file so next session can pick up in 30 seconds. + USE WHEN: user says "今天先到这" / "done for today" / "see you tomorrow" / "we'll continue later" / "下次再继续" / "end session" / "收尾", context about to compact, long task in progress, natural pause approaching (end of work day, end of milestone), sub-task in flight that outlives this session. + TRIGGER PHRASES: "今天先到这", "done for today", "see you tomorrow", "we'll continue later", "下次再继续", "先到这", "end session", "session 结束", "收尾", "写到 handoff file", "wrap up", "session handoff", "session 接力". + SKIP WHEN: session just started (no in-progress work to hand off), work is fully complete and verified (completion-audit passed), user said "throw it all away, start fresh next time" / "全部扔掉". +license: Apache-2.0 +compatibility: Requires MiniMax Code with Agent Plugins 1.0 support. +metadata: + author: antianqi + version: "0.1.1" + inspired-by: https://github.com/openai/codex/blob/main/codex-rs/state/src/runtime/recovery.rs and state/migrations/0047_rollout_migration_state.sql +--- + +# Session Handoff + +Sessions end. Time, context, attention, the user's day — all are finite. The default +end is "we'll pick it up next time," but that usually means "we'll figure out what we +were doing next time" — which costs the user another 5 minutes of re-orientation +every session, and costs you the context you built. + +This Skill makes session end a **first-class operation** with an explicit output: a +handoff file that the next session can `read` first, before any other action, and be +productive in 30 seconds instead of 5 minutes. + +## When to use + +Activate when **any** of these is true: + +- The user says "done for today" / "let's stop here" / "see you tomorrow" / "we'll + continue later". +- The context is about to be compacted or has grown large; you anticipate losing + context before the next user message. +- A long task is in progress and a "natural pause" is approaching (end of work day, + end of a logical milestone, user switch context). +- A sub-task is in flight (long-running build, background command) that will outlive + this session. + +## When NOT to use + +- The session just started. There is no in-progress work to hand off. +- The work is fully complete and verified (`completion-audit` passed). No + in-progress state to hand off. +- The user explicitly said "throw it all away, start fresh next time." Respect that. + +## Process + +1. **Confirm the handoff is wanted** (if you can — skip this step if the user is + clearly stepping away). One line: "Writing a handoff file so next session can + pick this up — okay?" +2. **Choose a single, predictable path.** Default: + `.minimax/handoff/-.md`. Different from world-state and + goal files (those describe current state; handoff is the **transition**). +3. **Write the handoff file** in this exact shape: + + ```markdown + # Handoff — + + **Written**: + **Session ended because**: + **Handed off to**: + + ## Goal (verbatim from the user) + + + + ## Current state (point-in-time snapshot) + + - — last updated + - — last updated + - — last updated + - — last updated + + ## What was done (this session) + + - [x] + - [x] + - [ ] + + ## In progress (when we stopped) + + - **What**: + - **Where we were**: + - **Next concrete step**: + - **Blocker (if any)**: + + ## Open questions for next session + + - + - + + ## Critical paths (read these first next session) + + - + - + - + + ## Things that might be wrong on resume + + - + - + - + ``` + +4. **Update the state files** (world-state, goal, family) to reference the handoff + file by path. The next session's `world-state-tracking` read should mention "see + handoff file X" so the next-session agent knows to read it first. + +5. **Tell the user, in one line**, where the handoff is: "Handoff written to + `.minimax/handoff/2026-08-24-xxx.md` — read this first next session." + +6. **If a sub-task is in flight** (background command, async build, etc.): + - Record its task_id, command, expected completion signal in the handoff. + - Do not assume the sub-task will complete; the next session may need to check. + +## Output contract + +The user sees, in this order: + +- One-line confirmation of the handoff path. +- The handoff file's contents (or a link to it). +- (If sub-task in flight) the task_id and how to check its status. +- (If open questions) the questions, so the user can answer them before the next session. + +## Example + +```markdown +# Handoff — Auth refactor (OIDC alongside SAML) + +**Written**: 2026-08-23T23:55:00Z +**Session ended because**: user said "we'll continue tomorrow" +**Handed off to**: next session — read this first + +## Goal (verbatim from the user) + +> "Refactor the auth subsystem to support OIDC without breaking the existing SAML path." + +## Current state (point-in-time snapshot) + +- `.minimax/goal/2026-08-23-auth-oidc.md` — last updated 2026-08-23T23:55:00Z +- `.minimax/state/auth-refactor.md` — last updated 2026-08-23T23:55:00Z +- `.minimax/family/auth-refactor.md` — last updated 2026-08-23T23:55:00Z + +## What was done (this session) + +- [x] Mapped current auth flow in `src/auth/` +- [x] Drafted `OidcProvider` trait + one impl for `provider = "okta"` +- [x] Confirmed 12/12 existing SAML tests still pass +- [ ] Add OIDC test for happy path with mock IdP +- [ ] Update `docs/auth.md` with new config flag + +## In progress (when we stopped) + +- **What**: writing the OIDC happy-path test +- **Where we were**: about to add the test fixture (decided to use `oauth2-mock-server`) +- **Next concrete step**: Create `tests/auth/oidc_test.rs` with a mock server fixture + and one login round-trip assertion +- **Blocker**: none + +## Open questions for next session + +- Should the OIDC module own token storage, or reuse the existing session store? +- Does IT have a preferred OIDC library (defaulting to `openidconnect`)? + +## Critical paths (read these first next session) + +- `/repo/src/auth/idp.rs` — current IdP interface +- `/repo/src/auth/oidc/mod.rs` — drafted OIDC implementation +- `.minimax/goal/2026-08-23-auth-oidc.md` — current goal +- `.minimax/state/auth-refactor.md` — current world state + +## Things that might be wrong on resume + +- `docs/auth.md` was last updated 3 days ago by another contributor; verify it still + describes SAML only before adding OIDC docs. +- The test fixture choice (`oauth2-mock-server`) is a recent decision; confirm with + the user before committing to it. +``` + +## Common pitfalls + +- **Do not write the handoff after every tool call.** It is a session-end operation, not + a checkpoint. +- **Do not skip the verbatim goal.** The next session does not have the user's voice + in context; the verbatim quote is the only way to recover the user's exact ask. +- **Do not write vague "next steps."** "Continue the work" is not a step. + "Create test file X with assertion Y" is. +- **Do not assume the sub-task will complete.** A background build can be killed + between sessions; the next session must check. +- **Do not put sensitive data in the handoff.** The file is on disk. Treat it like + any other workspace file. +- **Do not make the handoff the only place state lives.** The handoff **points to** + state files; the state files are the source of truth, the handoff is the index. + +## Verification checklist + +- [ ] Is the handoff at a single, predictable path (`.minimax/handoff/...`)? +- [ ] Is the goal section copied verbatim from the user? +- [ ] Are the "done" items ✅ with file paths, and the "in progress" items ⬜ with + exact "where we were" pointers? +- [ ] Is the "Next concrete step" one verb-first sentence? +- [ ] Are the critical paths listed (what the next session must read first)? +- [ ] Are the "might be wrong" risks named (so the next session verifies them)? +- [ ] If a sub-task is in flight, is its task_id and status-check method recorded? +- [ ] Did you tell the user where the handoff file is? +- [ ] Did you update the world-state file to reference the handoff path? diff --git a/plugins/antianqi/codex-harness-patterns/skills/skill-auto-select/SKILL.md b/plugins/antianqi/codex-harness-patterns/skills/skill-auto-select/SKILL.md new file mode 100644 index 0000000..515df5a --- /dev/null +++ b/plugins/antianqi/codex-harness-patterns/skills/skill-auto-select/SKILL.md @@ -0,0 +1,200 @@ +--- +name: skill-auto-select +description: | + Design a Skill (or Plugin) that an LLM agent can reliably discover, select, and invoke based on its description, with explicit selection syntax, name-collision handling, and three-layer matching. + USE WHEN: authoring a new skill for a Plugin, designing skill frontmatter, deciding between structured `UserInput::Skill` vs implicit `$skill-name` mention, handling duplicate skill names, picking between path-precise and name-based matching, or any task involving "make my skill actually get picked up by the agent". + TRIGGER PHRASES: "skill selection", "skill auto-pick", "$skill-name mention", "skill description", "skill metadata", "SkillMetadata", "ExplicitSkillLookup", "three-layer matching", "name collision", "ambiguous skill name". + SKIP WHEN: writing a one-shot script (use `error-recovery-strategy` or similar task skill), skill is human-only (no agent invocation), skill is bundled and not selectable. +license: Apache-2.0 +compatibility: Requires MiniMax Code with Agent Plugins 1.0 support. +metadata: + author: antianqi + version: "0.1.0" + inspired-by: https://github.com/openai/codex/tree/main/codex-rs/skills/ (P-85/86/87/88/89/92) + changes-from-v0.0.0: "Initial design distilled from P-85/86/87/88/89/92 deep-dive (Phase 1 Week 2)." +--- + +# Skill Auto-Select + +Design a Skill (or a whole Plugin) so an LLM agent can reliably discover it, decide +it is the right one, and invoke it. Mirrors the design of Codex's `codex-rs/skills/` +runtime, which is what this very Plugin is mimicking. + +## When to use + +Activate when designing: + +- A new skill's frontmatter (`name`, `description`, `short_description`, `interface`, `dependencies`, `policy`). +- A skill marketplace or registry where multiple skills may collide on name. +- A path-based discovery surface (logical discovery path vs canonical path). +- An explicit-vs-implicit invocation model (structured input vs `$name` mention vs shell command invocation). + +## When NOT to use + +- Skills that are bundled, not selectable (e.g. always-on system skills). Use a different distribution model. +- One-shot scripts that should never be auto-selected. Use task skills (`error-recovery-strategy`, `plan-stream-emit`). + +## Process + +### 1. Write the 11-field SkillMetadata + +Every skill should expose at minimum these fields: + +| Field | Type | Purpose | +|---|---|---| +| `name` | `String` (≤ 64 chars) | Canonical name, used in mentions and uniqueness checks. | +| `description` | `String` (≤ 128 chars) | One-line purpose, used by LLM to decide "is this for me?". | +| `short_description` | `Option` | UI label, used in lists. | +| `interface` | `Option` | UI metadata (`display_name`, `icon`, `brand_color`, `default_prompt`). | +| `dependencies` | `Option` | Declared external tools (MCP / function / etc). | +| `policy` | `Option` | `allow_implicit_invocation` (default `true`), `products`. | +| `path_to_skills_md` | `AbsolutePathBuf` | Host-side canonical path. | +| `scope` | `SkillScope` | Source: `User` / `System` / `Plugin` / etc. | +| `plugin_id` | `Option` | If from a marketplace plugin. | +| `remote_plugin_id` | `Option` | If remote. | +| (system) | `enabled` | Computed from `disabled_paths`. | + +In your frontmatter, the **only fields that matter for LLM matching** are `name` and +`description`. The other fields matter for the runtime. + +### 2. Write a keyword-greppable description (v0.6.1 format) + +```yaml +description: | + . + USE WHEN: . + TRIGGER PHRASES: . + SKIP WHEN: . +``` + +Why: + +- The LLM matches on real signals (`ECONNREFUSED`, `permission denied`, `retries exceeded`, "上下文满了" / "出错了" / "重试"), not abstract prose. +- `USE WHEN` and `TRIGGER PHRASES` are greppable substrings; `SKIP WHEN` reduces false positives. +- Bilingual (English + Chinese) descriptions match user language directly. + +### 3. Adopt three-layer matching + +When a user types `$skill-name` or `[$skill-name](path)`: + +```text +Layer 1 — canonical path: /path/to/skills/SKILL.md +Layer 2 — discovery path: skill://skill-name/SKILL.md (logical) +Layer 3 — plain name: skill-name (only if unambiguous) +``` + +Rules: + +- Layer 1 wins if path matches canonical. +- Layer 2 wins if path matches discovery path AND Layer 1 missed. +- Layer 3 wins ONLY if `skill_count == 1 && connector_count == 0` (uniqueness check via `name_counts`). +- If a structured `UserInput::Skill` already matched some name, **block** that name from Layer 3 (`blocked_plain_names`). + +Complexity target: `O(T + (N_s + N_t) * S)` time, `O(S + M)` space (T = text length, S = skill count, M = mentions per input). With ~20 skills and 1KB text, this is sub-millisecond. + +### 4. Provide explicit invocation syntax + +Two syntaxes, both supported: + +```text +$skill-name # plain +[$skill-name](skill://path/SKILL.md) # linked +``` + +Exclude environment variables from being mistaken for skills (`is_common_env_var($HOME)` → true, skip). Support the 5 tool mention kinds with 4 path prefixes: + +```text +app://app-id/... +mcp://server/tool +plugin://plugin-id/... +skill://skill-name/... +SKILL.md (literal filename) +``` + +### 5. Detect implicit invocation in shell commands + +Before doing the explicit three-layer match, also detect when a shell command references a skill script or document: + +```rust +detect_implicit_skill_invocation_for_command(outcome, command, workdir) +``` + +- Tokenize (Windows: PowerShell; Unix: shlex). +- Look for `python` / `node` / `bash` / `sh` / `pwsh` invocations. +- Look for `Read` operations on `scripts/` or `references/`. +- Match by path (scripts dir → skill) and by doc (read path → skill). + +### 6. Cache the loaded snapshot + +Use a `SkillRootSnapshotCache` trait so the loader can re-use a parsed snapshot: + +```rust +pub trait SkillRootSnapshotCache: Send + Sync { + fn get(&self, root: &Root) -> Option; + fn insert(&self, root: Root, snapshot: LoadedSkillRoot); +} +``` + +`SkillRootSnapshots` is `Arc>` with identity-based +`Hash` / `Eq` (uses `Arc::ptr_eq`). Cache key safety: clones share the same `Arc`, +so identity equality holds. + +### 7. Load with errors-as-data + +`LoadedSkillRoot { skills, errors: Vec, ... }` — never let one bad skill +kill the whole root. Collect errors and surface them at the top. + +## Output contract + +A skill that follows this design: + +- Has a 64-char-max `name` and a greppable 128-char-max `description`. +- Supports both `$name` plain and `[$name](path)` linked mention. +- Three-layer matching with uniqueness check on plain name. +- Implicit invocation detection in shell commands. +- Cached snapshot with identity-based hashing. +- Errors collected per-skill, never aborting the whole root. + +## Common pitfalls + +- **Plain name on a duplicate** → ambiguous; ignored. Always provide a path or qualify with the structured form. +- **Description too abstract** → LLM cannot match. Use the 4-line `USE WHEN / TRIGGER PHRASES / SKIP WHEN` format with concrete keywords. +- **Bypassing the uniqueness check** → two skills fire from one mention. Always require `skill_count == 1`. +- **Forgetting `is_common_env_var`** → `$HOME` / `$PATH` become "skill mentions". Filter them. +- **Loading all skills on every mention** → slow. Use `SkillRootSnapshotCache`. +- **Frontmatter name > 64 chars** → rejected by parser. Count your characters. +- **Skills with `description: ""` → MissingField error**. Description is mandatory. + +## Example — minimal frontmatter + +```yaml +--- +name: my-skill +description: | + Detect a specific failure mode in the running session and recover. + USE WHEN: ECONNREFUSED, permission denied, retries exceeded, "can't connect" / "出错了" / "重试" / "权限". + TRIGGER PHRASES: "recover", "retry failed", "switch tool", "ask me", "出错了", "重试". + SKIP WHEN: short task, in middle of dictating. +license: Apache-2.0 +compatibility: Requires MiniMax Code with Agent Plugins 1.0 support. +metadata: + author: you + version: "0.1.0" +--- + +# My Skill + +... the actual instructions ... +``` + +## Verification checklist + +- [ ] Frontmatter has `name` (≤ 64) and `description` (≤ 128, ≥ 1, non-empty after `sanitize_single_line`). +- [ ] Description uses the 4-line `USE WHEN / TRIGGER PHRASES / SKIP WHEN` format. +- [ ] Description is bilingual if your users write in multiple languages. +- [ ] Three-layer matching is implemented: canonical path → discovery path → unique plain name. +- [ ] `name_counts` is built once per selection and consulted for uniqueness. +- [ ] `is_common_env_var` filters out `$HOME` / `$PATH` etc. +- [ ] Implicit invocation detection tokenizes per-platform (PowerShell vs shlex). +- [ ] Snapshot cache is identity-based (`Arc::ptr_eq`). +- [ ] Load errors are collected per-skill, never abort the root. diff --git a/plugins/antianqi/codex-harness-patterns/skills/streaming-output-reader/SKILL.md b/plugins/antianqi/codex-harness-patterns/skills/streaming-output-reader/SKILL.md new file mode 100644 index 0000000..e54e235 --- /dev/null +++ b/plugins/antianqi/codex-harness-patterns/skills/streaming-output-reader/SKILL.md @@ -0,0 +1,155 @@ +--- +name: streaming-output-reader +description: | + Read long streaming responses in bounded chunks with cumulative summary, max 3 reads, never loop. + USE WHEN: tool returns long stream (SSE / WebSocket / `tail -f` / large log), output might be > 3000 tokens, file size unknown, previous read returned "truncated" / "use offset to read more" / "output cut off", `tail` of a growing log, "流式" / "实时" / "incremental" / "read in chunks". + TRIGGER PHRASES: "流式", "streaming", "实时", "tail -f", "real-time", "一边跑一边看", "log 在长", "output cut off", "读到一半卡了", "incremental", "stream-read", "read in chunks", "流式读取". + SKIP WHEN: output is small (<100 lines), output is structured and needs whole parse, polling for specific event (different pattern). +license: Apache-2.0 +compatibility: Requires MiniMax Code with Agent Plugins 1.0 support. +metadata: + author: antianqi + version: "0.1.1" + inspired-by: https://github.com/openai/codex/blob/main/codex-rs/core/src/client.rs and core/src/unified_exec/ +--- + +# Streaming Output Reader + +Long outputs kill conversations. A 50,000-line log dump will fill any context window +in one read. The reflex is to `tail` the file or `read` a small slice — but that often +misses the **earliest** lines (errors at the start of a long run) and requires multiple +round trips to get a complete picture. + +This Skill defines a single-pass read protocol: read in **bounded chunks**, keep a +**cumulative summary**, and stop when you have enough to act on. + +## When to use + +Activate when **any** of these is true: + +- A tool returns output that **might** be longer than ~3000 tokens (the `tool-output-budget` + threshold). Better to be cautious: read in chunks from the start. +- The tool offers an explicit streaming API (SSE, WebSocket chunks, `tail -f` with + follow-mode, log subscription) and you are not using it. +- You are about to read a file you do not control the size of (build logs, test logs, + process stdout, JSONL files). +- A previous read returned truncation / "output cut off" / "use offset to read more". + +## When NOT to use + +- The output is known to be small (< 100 lines). Just `read` or `cat` it in one go. +- The output is structured (JSON / CSV) and you need the **whole** thing to parse. Read + it once with a guard ("first 3000 lines"), then if you need more, do a second + targeted read. +- You are polling for a specific event ("did the file appear yet?"). That is a + different pattern (`background-task` Skill + `read` with a wait). + +## Process + +1. **Estimate before reading.** If you do not know the size, use a low-cost probe + (e.g. `wc -l file.log`, `ls -la file.log`, or `du -h`) to learn the byte / line count + before reading content. This prevents "oh, 200 MB" surprises. +2. **Pick a chunk policy.** Default: + - `head -N` for the first N lines (where N ≈ 200) — captures start-of-run errors + and configuration. + - `tail -N` for the last N lines (where N ≈ 50) — captures end-of-run status, + exit messages, final stack trace. + - `grep -B 2 -A 2 PATTERN` for targeted search — captures context around a known + error or marker. +3. **Read once, in chunks, do not loop.** Three reads in a single response is + acceptable; ten is not. After three reads, **stop and synthesize** — re-reading the + same file in different windows is a sign the data is too noisy for the current + approach, and the right answer is `ask-user` or `switch` to a different file. +4. **Maintain a cumulative summary as you read.** The summary is what the user sees; + the raw chunks are scaffolding. After each chunk, append a one-line takeaway to the + summary. The summary fits in your context; the raw output does not. +5. **Stop when you have enough to act on.** "Enough" means: + - You can state the file's purpose and current state in one sentence. + - You can identify any errors or anomalies. + - You can answer the user's original question, OR you know what you would need to + read next to answer it (and the next read is targeted, not another dump). +6. **If you cannot stop**, surface to the user with the cumulative summary, the + question, and the trade-off (read more context? read a different file? ask the user?). + +## Output contract + +The user sees, in this order: + +- One-line estimate of the file / stream size (if probed). +- A cumulative summary in this shape: + + ```text + Stream summary: + Chunks read: (head 200 / tail 50 / grep 1) + Key findings: + - : + - : + Next step: + ``` + +- (If you stopped because of step 6) the question and trade-off, not a silent loop. + +## Example + +```text +> wc -l /var/log/app/today.log +4287 /var/log/app/today.log + +> head -200 /var/log/app/today.log +[200 lines read; key entries captured below] + +> grep -B 2 -A 5 "ERROR" /var/log/app/today.log | tail -50 +[50 lines of error context; summary below] + +Stream summary: application startup with a 10-minute error burst at 14:23 +Chunks read: 3 (head 200 / tail 50 / grep 1) +Key findings: +- 14:23:00 INFO service starting +- 14:23:14 ERROR connect ECONNREFUSED 10.0.0.5:5432 (database) +- 14:23:14 ERROR retry 1/3 ... +- 14:23:23 ERROR retry 3/3 exhausted; service degraded +- 14:33:00 WARN service continuing in degraded mode (DB read-only) + +Next step: investigate why 10.0.0.5:5432 was unreachable at 14:23; the +service is now in a degraded state and the user may need to restart. +``` + +Counter-example (read too much, lost the thread): + +```text +> read /var/log/app/today.log +[4000 lines, ~50K tokens; everything from the read is now in context] + +> read /var/log/app/today.log +[duplicate, same 4000 lines] + +> read /var/log/app/today.log +[duplicate, same 4000 lines — context is now full of redundant log] +``` + +The counter-example is the failure mode this Skill prevents. Three reads, no synthesis, +context full, no actionable conclusion. + +## Common pitfalls + +- **Do not `read` an unbounded file in one call.** Use `head`, `tail`, or `grep` first. +- **Do not loop reads.** Three is a hard cap. After three, you owe the user a + synthesis, not a fourth read. +- **Do not skip the summary.** The raw chunks are scaffolding; the summary is the + product. Without a summary, the user has to re-read everything you read. +- **Do not guess from a single chunk.** If the file is structured (timestamps, log + levels), use grep to anchor on the structure, not just head/tail. +- **Do not re-read the same range.** If `head -200` did not show what you needed, do + not read `head -200` again; read `grep PATTERN` or `sed -n '200,400p'`. +- **Do not stream to the user's chat verbatim.** The user wants the synthesis, not + the raw bytes. Stream is for you; summary is for them. + +## Verification checklist + +- [ ] Did you estimate size before reading (if size was unknown)? +- [ ] Did you use a chunk policy (head / tail / grep) rather than a single `read`? +- [ ] Did you write a cumulative summary as you went, not after? +- [ ] Did you stop after at most 3 reads, even if you did not have the answer? +- [ ] Is the summary one-line purpose + findings + next step, not raw output? +- [ ] Did you surface to the user (with the question) if you could not stop on your own? +- [ ] Did you avoid re-reading the same range? diff --git a/plugins/antianqi/codex-harness-patterns/skills/subagent-family-tracking/SKILL.md b/plugins/antianqi/codex-harness-patterns/skills/subagent-family-tracking/SKILL.md new file mode 100644 index 0000000..377fb1c --- /dev/null +++ b/plugins/antianqi/codex-harness-patterns/skills/subagent-family-tracking/SKILL.md @@ -0,0 +1,157 @@ +--- +name: subagent-family-tracking +description: | + Track parent/child thread tree of spawned sub-agents with Open/Closed status. + USE WHEN: spawned one or more sub-agents, task description suggests a tree (sub-tasks, "for each of A/B/C", "5 stages"), user asks "what's your sub-agent doing right now" / "子 agent 都在干嘛", sub-agent may fan out, want to know "还在跑吗" / "还有几个没关", before declaring fan-out done (all children closed check). + TRIGGER PHRASES: "子 agent 都在干嘛", "sub-agent", "子任务", "family tree", "who is running", "还在跑吗", "还有几个没关", "what's your sub-agent doing", "subagent family", "subagent tree", "all children closed". + SKIP WHEN: sub-task is so cheap you'd just inline it, harness already exposes live sub-agent dashboard, you are the child not the parent. +license: Apache-2.0 +compatibility: Requires MiniMax Code with Agent Plugins 1.0 support. +metadata: + author: antianqi + version: "0.1.1" + inspired-by: https://github.com/openai/codex/blob/main/codex-rs/agent-graph-store/ +--- + +# Sub-Agent Family Tracking + +When you spawn sub-agents, you create a **family tree**: you are the parent, your spawned +agents are children, and your children's spawned agents are grandchildren. If you do not +track this tree explicitly, three failure modes are common: + +1. **Lost child** — you spawn an agent, lose track of its `task_id`, and never read its + result. +2. **Sibling duplication** — two children of yours do the same work, wasting tokens. +3. **Lingering child** — you move on to the next sub-task, leaving an old child running + in the background, burning context budget. + +This Skill codifies a **family-tracking file** so these failures are visible, not hidden. + +## When to use + +Activate when **any** of these is true: + +- You are about to call `task` and the result of that call is "fire and wait," not "do it + inline." (Inline calls don't need a tree.) +- You have already spawned one or more sub-agents in this session. +- The task description suggests a tree: "X has sub-tasks", "for each of A/B/C, ...", + "the migration has 5 stages, each stage can be parallelised." +- A user asks "what's your sub-agent doing right now?" and you don't have an immediate + answer. + +## When NOT to use + +- The sub-task is so cheap you would just inline it. (No sub-agent → no tree.) +- The harness already exposes a live dashboard of running sub-agents. (Use that instead.) +- You are the *child*, not the parent. Children don't track siblings; they only see their + own parent. + +## Process + +1. **Pick a single, predictable path.** Default: + `.minimax/agents//subagents.md`. Different from the goal file (which is "what we + are doing") and the world-state file (which is "where we are"); this is "who is + working for us." +2. **Initialise the file on the first spawn** in this exact shape: + + ```markdown + # Sub-agent family — + + **Parent (you)**: + **Last updated**: + + ## Children + + | id | spawned_at | brief | status | result_summary | + |----|------------|-------|--------|----------------| + | | | | open | (pending) | + ``` + +3. **On every spawn**, add a row with `status: open` and a one-line brief. +4. **On every sub-agent completion**, update the row: + - `status: closed` (or `closed-failed` if it didn't meet its pass condition) + - `result_summary: ` +5. **If a sub-agent spawns its own children**, append a sub-section for that child: + + ```markdown + | | | | open | (pending) | + ## Children of + | grandchild-id | spawned_at | brief | status | result_summary | + |---------------|------------|-------|--------|----------------| + ``` + +6. **At every `context-pressure-compact`**, the compact summary must reference the family + file by path, not duplicate it. The file is the ground truth. +7. **At the end of a fan-out aggregation** (see `parallel-fanout`), include a one-line + "all children closed" check. If any are still `open`, surface that to the user — the + aggregation is not safe to declare done while children are running. + +## Output contract + +The user sees: + +- The family file's contents (full) at the start of any multi-agent task. +- A one-line status update per spawn / completion (e.g. "spawned task-7a3f for OIDC review," + "task-7a3f closed with PASS"). +- A "all children closed" check at the end of any fan-out. + +## Example + +```markdown +# Sub-agent family — Auth refactor (OIDC alongside SAML) + +**Parent (you)**: thread-7c2b +**Last updated**: 2026-08-23T23:55:00Z + +## Children + +| id | spawned_at | brief | status | result_summary | +|----|------------|-------|--------|----------------| +| task-3f1a | 23:40:00Z | Audit /repo/server/Cargo.toml for CVEs | closed | No critical CVEs; 2 moderate, listed in SECURITY-REPORT.md | +| task-4d2b | 23:40:00Z | Audit /repo/web/package.json for CVEs | closed | 1 critical CVE (CVE-2024-xxxx); surfaced immediately, did not wait for others | +| task-5e3c | 23:40:00Z | List licenses of /repo/server and /repo/web direct deps | open | (pending) | +``` + +After task-5e3c finishes: + +```markdown +| task-5e3c | 23:40:00Z | List licenses of /repo/server and /repo/web direct deps | closed | 47 deps; 1 AGPL, 4 Apache-2.0, rest MIT — full table in SECURITY-REPORT.md | +``` + +If task-5e3c spawned a grandchild (e.g. for cross-checking an ambiguous license): + +```markdown +| task-5e3c | 23:40:00Z | List licenses of /repo/server and /repo/web direct deps | closed | 47 deps; 1 AGPL, 4 Apache-2.0, rest MIT | + ## Children of task-5e3c + | task-5e3c-1 | 23:42:00Z | Cross-check `foo-1.0.0` license classification | closed | Confirmed AGPL-3.0 via SPDX registry | +``` + +## Common pitfalls + +- **Do not skip the file "because it's only one sub-agent."** You will spawn another + before you know it, and then you will not know which results came from which. +- **Do not put full sub-agent output in the table.** The table is the index. The full + output lives in the sub-agent's own response (or a file it wrote). The table points to it. +- **Do not let the file grow unbounded.** A 500-line family file is no longer a tracking + file. If it grows past ~80 lines, summarise closed children into a "Completed + (summary)" section. +- **Do not confuse "open" with "running".** A sub-agent can be `open` and waiting on user + input, or `open` and silently failed. "Closed" means the result was received and + processed, not that the work succeeded. +- **Do not declare fan-out done while children are still open.** A `parallel-fanout` aggregation + must wait for *all* children to close. The check is mechanical, not visual. +- **Do not let a child spawn grand-children without recording it.** If you do not capture + the grandchild relationship, you cannot tell which child is responsible for which + grandchild's result. + +## Verification checklist + +- [ ] Is the family file at a single, predictable path? +- [ ] Is it initialised with the full table shape on the first spawn? +- [ ] Does every spawn add a row with `status: open`? +- [ ] Does every completion update the row to `status: closed`? +- [ ] Are grand-children recorded in a sub-section under their parent? +- [ ] Is the file < ~80 lines? (Summarise old entries if not.) +- [ ] At every `context-pressure-compact`, is the file referenced by path (not + duplicated)? +- [ ] At the end of any fan-out, is the "all children closed" check explicit? diff --git a/plugins/antianqi/codex-harness-patterns/skills/tool-discovery-pattern/SKILL.md b/plugins/antianqi/codex-harness-patterns/skills/tool-discovery-pattern/SKILL.md new file mode 100644 index 0000000..bebf247 --- /dev/null +++ b/plugins/antianqi/codex-harness-patterns/skills/tool-discovery-pattern/SKILL.md @@ -0,0 +1,245 @@ +--- +name: tool-discovery-pattern +description: | + Design a tool that an LLM agent can reliably discover, search, and invoke — with proper schema, defer_loading, two-dimensional type classification, OpenAI protocol compatibility, and a tool-suggestion approval flow. + USE WHEN: writing a new tool for an agent, designing the JSON schema for a tool, deciding between Function / Freeform / Namespace, fixing MCP tools that don't work with OpenAI models, building a tool-search index, designing a "request plugin install" flow, or any task involving "make my tool actually get picked up by the agent". + TRIGGER PHRASES: "tool discovery", "tool search", "tool spec", "DiscoverableTool", "defer_loading", "tool_suggestion", "request_plugin_install", "MCP tool", "Dynamic tool", "JSON schema for tool", "responses API tool", "ResponsesApiFunctionTool", "ResponsesApiCustomTool", "ResponsesApiNamespace". + SKIP WHEN: writing a Skill (use `skill-auto-select`), building a plugin manifest (use `plugin-author-helper`), single-use CLI script (not a tool). +license: Apache-2.0 +compatibility: Requires MiniMax Code with Agent Plugins 1.0 support. +metadata: + author: antianqi + version: "0.1.0" + inspired-by: https://github.com/openai/codex/tree/main/codex-rs/tools/ (P-107-114) + changes-from-v0.0.0: "Initial design distilled from P-107-114 deep-dive (Phase 2 Week 6)." +--- + +# Tool Discovery Pattern + +Design a tool that an LLM agent can discover, search, decide to use, and invoke. +Mirrors the design of `codex-rs/tools/`. + +## When to use + +Activate when designing: + +- A new tool's JSON schema. +- The choice between Function (structured) / Freeform (custom) / Namespace (container) tool types. +- A search index over a large tool catalog. +- A "request plugin install" suggestion flow. +- Schema compatibility with OpenAI models. + +## When NOT to use + +- Skill authoring → use `skill-auto-select`. +- Plugin manifest authoring → use `plugin-author-helper`. +- Single-use scripts → not a tool. + +## Process + +### 1. Two-dimensional type classification + +```rust +pub enum DiscoverableToolType { Connector, Plugin } +pub enum DiscoverableToolAction { Install, Enable } + +pub enum DiscoverableTool { + Connector(Box), + Plugin(Box), +} +``` + +**Any discoverable item is the cartesian product of (Type) × (Action)**. Adopt this +orthogonal taxonomy so a single `request_plugin_install(Connector, Install, ...)` +and `request_plugin_install(Plugin, Enable, ...)` work the same way. + +### 2. Pick the right tool shape + +For OpenAI Responses API, three shapes: + +| Shape | When to use | +|---|---| +| `ResponsesApiFunctionTool` | Structured input schema, typed args. | +| `ResponsesApiCustomTool` | Freeform input, agent decides. | +| `ResponsesApiNamespace` | Container of multiple tools (e.g. all functions). | + +Use Namespace to group related tools so the model sees one entry, not ten. + +### 3. Write the 7-type JSON schema + +OpenAI Structured Outputs supports exactly these `type` values: + +```text +string | number | boolean | integer | object | array | null +``` + +Plus these composition keywords: + +```text +anyOf | oneOf | allOf +$ref | enum | const | properties | required | description +``` + +Support both single-type (`"string"`) and multi-type (`["string", "null"]`) via: + +```rust +pub enum JsonSchemaType { + Single(JsonSchemaPrimitiveType), + Multiple(Vec), +} +``` + +**Do not** support the full JSON Schema spec. Stick to the OpenAI subset. + +### 4. Use BTreeMap for stable output + +For any user-visible schema, use `BTreeMap` not `HashMap`. Stable iteration order = stable JSON output = no spurious git diffs. + +### 5. Adopt the defer_loading pattern + +If you have many tools, expose them through a search index with `defer_loading: true`: + +```text +[searchable] tools are exposed as Namespace entries containing: + - name (short) + - description (1-line) + - defer_loading: true ← schema is loaded only when the agent decides to use it +``` + +The agent sees a lightweight description, and the full `input_schema` is fetched only +on actual invocation. This prevents schema bloat from filling the context. + +### 6. Apply the OpenAI compatibility fix + +OpenAI models REQUIRE the `properties` field on any object schema. Many MCP servers +omit it. Patch it on load: + +```rust +if obj.get("properties").is_none_or(Value::is_null) { + obj.insert("properties".into(), Value::Object(Map::new())); +} +``` + +This matches the OpenAI Agents SDK behavior. Always apply on the host side, never +ask the upstream server to fix it. + +### 7. Truncate descriptions at char boundaries + +For agent plugins, cap descriptions at 1 KB: + +```rust +const MAX_MCP_TOOL_DESCRIPTION_BYTES: usize = 1_000; +take_bytes_at_char_boundary(description, limit) +``` + +Use **byte** boundary, not char. Char truncation can split a multi-byte UTF-8 +codepoint and produce invalid strings. + +### 8. Provide a tool-search tool + +Expose a top-level `tool_search` tool: + +```rust +pub const TOOL_SEARCH_TOOL_NAME: &str = "tool_search"; +pub const TOOL_SEARCH_DEFAULT_LIMIT: usize = 8; +``` + +The tool takes a query string and returns a list of `LoadableToolSpec` entries with +`defer_loading: true`. Each returned entry is wrapped in a `Namespace` with +`DEFAULT_FUNCTION_NAMESPACE`. + +### 9. Provide a `request_plugin_install` tool + +When the agent encounters a tool it doesn't have, it should be able to suggest installing it: + +```rust +pub struct RequestPluginInstallArgs { + pub tool_type: DiscoverableToolType, // Connector | Plugin + pub action_type: DiscoverableToolAction, // Install | Enable + pub tool_id: String, + pub suggest_reason: String, // mandatory: WHY does the agent need this? +} + +pub struct RequestPluginInstallResult { + pub completed: bool, + pub user_confirmed: bool, + pub tool_name: String, + // ... +} +``` + +The approval is tagged with `codex_approval_kind = "tool_suggestion"` so the UI +can present it as a suggestion, not a regular command approval. The +`persist: "always"` flag means once the user accepts, it's always allowed. + +The `suggest_reason` field is **mandatory** — the agent must explain why it needs +this tool, not silently suggest. This prevents runaway tool installations. + +### 10. Mark namespace descriptions + +If a Namespace has an empty description, fill it in with a default: + +```rust +if namespace.description.trim().is_empty() { + namespace.description = default_namespace_description(&namespace.name); +} +``` + +Don't ship a tool with an empty description. + +## Output contract + +A tool that follows this design: + +- Has a 7-type JSON schema (no exotic types). +- Has a BTreeMap-ordered schema. +- Uses Namespace to group related tools. +- Has `defer_loading: true` when surfaced through search. +- Has `properties` always present in object schemas. +- Description is ≤ 1KB for agent plugin tools, truncated at byte boundaries. +- Has a top-level `tool_search` tool for search. +- Has a `request_plugin_install` tool with mandatory `suggest_reason`. + +## Common pitfalls + +- **Empty `description`** → LLM can't decide if this tool fits. Always fill in (use `default_namespace_description` if needed). +- **Schema with non-OpenAI types** (`"date"`, `"uri"`, `"regex"`, ...) → rejected by model. Stay in the 7-type subset. +- **Object schema missing `properties`** → OpenAI rejects. Always patch. +- **`HashMap` schema** → unstable JSON output. Use `BTreeMap`. +- **`defer_loading: false` for hundreds of tools** → context explodes. Search index + defer is the answer. +- **`request_plugin_install` without `suggest_reason`** → runaway installation. Require the field. +- **Approval tagged as plain command** → wrong UI. Use `codex_approval_kind = "tool_suggestion"`. +- **Char-boundary truncation** → splits UTF-8. Use `take_bytes_at_char_boundary`. + +## Example — minimal tool manifest + +```json +{ + "name": "list_pipelines", + "description": "List all data pipelines in the warehouse, optionally filtered by status.", + "input_schema": { + "type": "object", + "properties": { + "status": { + "type": "string", + "enum": ["running", "paused", "failed", "all"], + "default": "all" + } + }, + "required": [] + }, + "defer_loading": true +} +``` + +## Verification checklist + +- [ ] All object schemas have a `properties` field. +- [ ] All type fields are in the 7-type subset. +- [ ] All enums are arrays of strings. +- [ ] Schemas use `BTreeMap` not `HashMap`. +- [ ] Tools exposed through search are in Namespaces with `defer_loading: true`. +- [ ] `request_plugin_install` requires `suggest_reason` and tags `tool_suggestion` approval. +- [ ] Agent plugin tool descriptions are ≤ 1KB, truncated at byte boundaries. +- [ ] Empty `description` is auto-filled with `default_namespace_description`. +- [ ] `tool_search` tool is exposed at the top level. diff --git a/plugins/antianqi/codex-harness-patterns/skills/tool-output-budget/SKILL.md b/plugins/antianqi/codex-harness-patterns/skills/tool-output-budget/SKILL.md new file mode 100644 index 0000000..54af6d1 --- /dev/null +++ b/plugins/antianqi/codex-harness-patterns/skills/tool-output-budget/SKILL.md @@ -0,0 +1,111 @@ +--- +name: tool-output-budget +description: | + Truncate oversized tool output so it does not blow the agent's context window. + USE WHEN: tool output > 3000 tokens, line > 500 chars, large log, JSON array, minified code, fetched HTML, verbose npm/cargo/test output, `cat` of a big file, "truncated" / "output cut off" / "use offset to read more" message. + TRIGGER PHRASES: "输出太长", "context 满了", "log 太大", "截断", "truncate", "output cut off", "读不完", "太大了", "context 撑爆", "too long". + SKIP WHEN: output is small (<100 lines), output is the user-facing final answer, output is structured and needs full parse (read once with a guard). +license: Apache-2.0 +compatibility: Requires MiniMax Code with Agent Plugins 1.0 support. +metadata: + author: antianqi + version: "0.1.1" + inspired-by: https://github.com/openai/codex/tree/main/codex-rs/utils/output-truncation +--- + +# Tool Output Budget + +Keep oversized tool output out of the main context. Replace it with a token-aware summary plus the +parts most likely to matter. + +## When to use + +Use this Skill **immediately after** any of these tool calls, before quoting the output in your +next response: + +- `bash` returns output that looks like a large log, JSON array, HTML page, or `cat` of a long + file (e.g. `cat huge.log`, `npm test 2>&1`, `kubectl get ... -o yaml`). +- Any tool returns a single line longer than ~500 characters (typical of minified JS, base64 + blobs, very wide CSV). +- The tool succeeded but the user did not ask for the full payload. + +## When NOT to use + +- The user explicitly asked for "the whole thing" or "every line". +- The output is small (< ~3000 tokens). Trust the tool as-is. +- You genuinely need the exact bytes (e.g. computing a hash, doing a byte-equal diff). Quote the + output verbatim and explain why truncation is unsafe. + +## Process + +1. **Estimate the size.** If the tool already returned a byte count, use that. Otherwise, count + newlines and pick the longest line. A line of ~80 characters is roughly 20 tokens. +2. **Decide to truncate if** any of the following hold: + - Total estimated tokens > 3000 (default threshold). + - Any single line > 500 characters. + - Output structure is a long JSON array, log dump, or fetched HTML. +3. **Pick what to keep.** Default: **head 200 lines + tail 50 lines + 1 marker line**. Adapt: + - For `cat` of a known file, prefer **tail** (errors and stack traces usually live at the end). + - For a JSON array, prefer **head** (the schema is at the top; the agent only needs a few + sample items). + - For a fetched HTML page, prefer the **head** (metadata, title) plus a 1-line tag count + summary; do not keep the body. +4. **Format the replacement** as a single fenced block: + + ```text + [truncated by tool-output-budget] + kept: lines 1-200 of 1234 (head) + lines 1185-1234 of 1234 (tail) + skipped: 834 middle lines + estimated total tokens: ~5500 + file path on disk: # only if the tool output came from a real file + ``` + +5. **Hand back the original file path when you have it.** The user may want you to re-read a + specific range later, and you can use `read` with explicit `offset` / `limit` for that. +6. **Do not silently retry the same tool.** Truncation is a one-shot decision per tool result. + +## Output contract + +Every time you apply this Skill, the user's next message should see exactly this shape: + +- A short preamble explaining what was truncated and why (one sentence). +- A fenced code block with the kept head + tail + marker line. +- (Optional, only if a real file exists) The path to re-read for the full content. + +## Example + +```text +> cat /var/log/app.log + +[truncated by tool-output-budget] +kept: lines 1-200 of 4821 (head) + lines 4771-4821 of 4821 (tail) +skipped: 4621 middle lines +estimated total tokens: ~6800 +file path on disk: /var/log/app.log + +# last 5 of tail (the part the user usually wants) +2026-08-23T22:47:01Z ERROR connection refused 10.0.4.7:5432 +2026-08-23T22:47:02Z ERROR retrying (1/3) +2026-08-23T22:47:05Z ERROR connection refused 10.0.4.7:5432 +2026-08-23T22:47:08Z WARN giving up; will retry on next request +2026-08-23T22:47:08Z INFO request 7d4e failed: upstream unavailable +``` + +## Common pitfalls + +- **Don't truncate error output that contains the answer.** If the user asked "why did the test + fail", the failure line is in the tail of the test runner output. Keep tail, drop head. +- **Don't truncate `cat` of source code you need to edit.** Use `read` with `offset`/`limit` for + surgical access; keep the full code in the next step's tool call. +- **Don't estimate size from newlines alone.** A 1-line 50 KB minified file is one "line" but + ~12,000 tokens. Check the longest line first. +- **Don't loop.** If you truncated, the next tool call should *act* on the result, not re-run + the same command with the same expectation. + +## Verification checklist + +- [ ] Did you estimate tokens or bytes before deciding? +- [ ] Is the marker line present and clear about how much was skipped? +- [ ] Did you keep the part most likely to matter (head for structure, tail for errors)? +- [ ] If a real file path exists, did you hand it back to the user? +- [ ] Did the user's next step actually use the truncated result? diff --git a/plugins/antianqi/codex-harness-patterns/skills/world-state-tracking/SKILL.md b/plugins/antianqi/codex-harness-patterns/skills/world-state-tracking/SKILL.md new file mode 100644 index 0000000..6ce8f47 --- /dev/null +++ b/plugins/antianqi/codex-harness-patterns/skills/world-state-tracking/SKILL.md @@ -0,0 +1,193 @@ +--- +name: world-state-tracking +description: | + Track running state of long task in a single dedicated file that survives compaction. + USE WHEN: task is long, agent has lost thread, user asks "where are we" / "到哪了" / "我们到哪了", before `context-pressure-compact`, `todowrite` alone is too thin, agent has done > 10 tool calls, "lost the thread" / "继续" / "忘了". + TRIGGER PHRASES: "where are we", "到哪了", "我们到哪了", "继续", "lost thread", "忘了", "lost the thread", "我们刚才说到哪了", "走神了", "回到主线". + SKIP WHEN: short task (<5 tool calls), single one-shot question, "do X" with X being small. +license: Apache-2.0 +compatibility: Requires MiniMax Code with Agent Plugins 1.0 support. +metadata: + author: antianqi + version: "0.1.1" + inspired-by: https://github.com/openai/codex/blob/main/codex-rs/core/src/context/world_state.rs +--- + +# World State Tracking + +Keep a single dedicated state file that records the *shape* of the running task — the goal, +the decisions, the blockers, the next step, the key paths. Unlike the conversation history, +this file is **structured, finite, and survives compaction**. It is the agent's answer to +"where are we?" when the context window is full. + +## When to use + +Activate when **any** of these is true: + +- The task is long enough that the agent has lost the thread at least once already. +- The user asks "where are we?", "what's the status?", "are we still on track?", or "remind + me what we decided". +- You are about to apply `context-pressure-compact` (the state file is what survives it). +- The task has 3+ open decisions whose rationale you don't want to re-derive every turn. +- Multiple sub-agents (or the user and the agent) need a shared ground truth. + +## When NOT to use + +- A short task (< 5 turns, no major decisions yet). The state file is overhead. +- The whole task fits in a `todowrite`. Use that instead — it is already structured state. +- The state would duplicate information that lives in source files (e.g. the migration + plan already lives in `docs/migrations/auth.md`; do not restate it here). + +## Process + +1. **Pick a single, predictable path.** Default: + `.minimax/state/-.md` (or, for repos without a working dir, a + tmp file under `/tmp/`). The path is part of the contract — re-read it from the same + place every turn. +2. **Initialise the file the first time you activate this Skill.** Use this exact shape: + + ```markdown + # World State — + + **Started**: + **Owner**: + **Last updated**: + + ## Goal + + + + ## Current phase + + + + ## Decisions (with one-line rationale) + + - + - + + ## Done + + - [x] + - [x] <...> + + ## In progress + + - [ ] + + ## Blockers / open questions + + - + + ## Next concrete step + + + + ## Key file paths + + - /abs/path/that/the/next/turn/will/need + ``` + +3. **Update the file at every meaningful boundary**, not every turn. Boundaries are: + - End of a `todowrite` step. + - End of a sub-task handed to a `task` call. + - Right before a `context-pressure-compact`. + - Immediately after a user redirection. + At each update: bump `Last updated`, move items between Done / In progress, add new + Decisions, and refresh the Next concrete step. +4. **On every new turn, read the file first** (use `read`). It is your 30-line ground truth. + Do not skim the conversation history to "get back up to speed" — read the state file. +5. **At `context-pressure-compact` time**, the state file is what survives — the noisy + middle does not. The compact summary should reference the state file by path, not + duplicate its contents. + +## Output contract + +The user sees: + +- The path to the state file (one line, at the top of any meaningful response). +- On request: a short, *complete* snapshot of the state (the file contents, optionally + abbreviated). +- On update: a one-line "State updated: ". + +## Example + +```markdown +# World State — Auth refactor (OIDC alongside SAML) + +**Started**: 2026-08-23 +**Owner**: main +**Last updated**: 2026-08-23T23:55:00Z + +## Goal + +Refactor the auth subsystem to support OIDC as a first-class provider alongside the existing +SAML path, without breaking any of the 12 existing SAML tests. + +## Current phase + +planning + +## Decisions (with one-line rationale) + +- Keep SAML on the legacy code path; OIDC gets a parallel module. — SAML contract is frozen, + no test budget to re-validate. +- Reject "generic Provider with config-driven dispatch" — too much config surface for + marginal benefit. +- Use the `openidconnect` crate (not hand-rolled oauth2). — JWKS, PKCE, state, nonce all + solved; saves ~400 lines. + +## Done + +- [x] Mapped current auth flow in `src/auth/`. Wrote findings to `.minimax/snapshots/auth-flow.md`. +- [x] Confirmed test coverage: 12 of 14 files have unit tests (2 missing: `logout`, `session`). + +## In progress + +- [ ] Drafting the `OidcProvider` trait. Stopped at: how to represent the provider enum + vs the existing `IdP` interface. Three options on the table; see Open questions. + +## Blockers / open questions + +- Should the OIDC module own token storage, or reuse the existing session store? +- Does IT have a preferred OIDC library? (defaulting to `openidconnect`) + +## Next concrete step + +Draft the `OidcProvider` trait + one impl for `provider = "okta"`, then show the diff to +the user before touching `src/auth/callback.rs`. + +## Key file paths + +- /repo/src/auth/idp.rs +- /repo/src/auth/callback.rs +- /repo/tests/auth/ +- /repo/docs/auth.md +``` + +## Common pitfalls + +- **Do not put prose in the state file.** Prose is what the conversation history is for. The + state file is structured, finite, and machine-grepable. +- **Do not update on every turn.** Update at boundaries. A state file that changes every + line is just a noisy transcript. +- **Do not duplicate source-of-truth info.** If the API contract lives in `docs/api.md`, + the state file says "see docs/api.md", not "the API contract is ...". +- **Do not let the state file grow unbounded.** A 500-line state file is no longer a state + file; it is a journal. If it grows past ~80 lines, split it (e.g. `state.md` + + `decisions.md`) or compact. +- **Do not forget the path.** If you can name the path from memory every turn, the state + file is doing its job. If you cannot, move it to a more obvious place. +- **Do not use the state file as a substitute for `todowrite`.** The state file is the + long-form ground truth; `todowrite` is the short-form live checklist. They coexist. + +## Verification checklist + +- [ ] Is the state file at a single, predictable path the agent can name from memory? +- [ ] Is it initialised with the full 9-section shape on first activation? +- [ ] Is it updated at boundaries (steps, sub-tasks, compactions, redirections), not every + turn? +- [ ] Does every new turn start with `read` of the state file, not a conversation skim? +- [ ] Is the state file < ~80 lines? If not, split or compact. +- [ ] Does `context-pressure-compact` reference the state file by path, not duplicate it? +- [ ] Can a new sub-agent orient itself in < 30 seconds by reading only the state file? diff --git a/test/codex-harness-patterns.test.mjs b/test/codex-harness-patterns.test.mjs new file mode 100644 index 0000000..edefbd6 --- /dev/null +++ b/test/codex-harness-patterns.test.mjs @@ -0,0 +1,653 @@ +// codex-harness-patterns.test.mjs +// +// PR #18 review round 2: a static check that every one of the 23 +// `plugins/antianqi/codex-harness-patterns/skills/*/SKILL.md` files has +// exactly one valid YAML frontmatter block, with the required fields, and +// without a duplicate `author:` or `version:` key anywhere in the file. Also +// pins the mcode 0.2.4 tool-surface contract for the 5 Skills that touch the +// `task` / `bash` tools (no `subagent_type=` / `brief=` / `history=` / +// `bash(task_name=)` / `bash(action=)` / `model_config_id=` placeholders). +// +// Pinned against the bundled mcode 0.2.4 `cli.js` schema: +// - `task(description, prompt, agent_name, run_in_background?)` +// - `bash(command, timeout?, run_in_background?)` +// - `task_query(task_id?, status?)` +// - `task_output(task_id, offset?)` +// - `task_stop(task_id, reason?)` +// `agent_name=` is the canonical mcode 0.2.4 form. The Skills prefer the +// canonical form; this test fails if any Skill body uses `subagent_type=` +// inside a `task(` call. Mentioning `subagent_type` in prose (e.g. the +// `compatibility:` field) is allowed. + +import { test } from 'node:test'; +import assert from 'node:assert/strict'; +import { readdirSync, readFileSync, statSync, existsSync } from 'node:fs'; +import { join, resolve, dirname } from 'node:path'; +import { fileURLToPath } from 'node:url'; + +const REPO_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), '..'); +const PLUGIN_DIR = join(REPO_ROOT, 'plugins', 'antianqi', 'codex-harness-patterns'); +const SKILLS_ROOT = join(PLUGIN_DIR, 'skills'); + +// --- Helpers ---------------------------------------------------------------- + +function listSkillFiles() { + return readdirSync(SKILLS_ROOT, { withFileTypes: true }) + .filter((d) => d.isDirectory()) + .map((d) => ({ + name: d.name, + path: join(SKILLS_ROOT, d.name, 'SKILL.md'), + })) + .filter((d) => { + try { return statSync(d.path).isFile(); } catch { return false; } + }); +} + +// Very small YAML frontmatter parser. Supports the shape used by every +// Skill in this plugin: top-level `key: scalar` pairs, and a single +// `metadata:` block whose body is indented 2 spaces and contains +// `key: scalar` pairs. Returns the parsed object plus a list of (line, +// key) entries in document order so we can detect duplicates. +function parseFrontmatter(text) { + // Normalize line endings to LF. Skill files on Windows are commonly + // written with CRLF; if we don't normalize first, line 53's strict + // `text.startsWith('---\n')` check fails on every Skill, and the + // inner-`---` regex (line 67) silently misses lines that end in \r + // because `$` is anchored to the position before \n, not before \r. + // Round-5 finding: frontmatter uniqueness check previously "only + // counted lines exactly equal to `---`" because the regex /\s*---\s*$/ + // didn't match `\r`-terminated lines; this normalization closes that + // hole by making the parser see a single canonical line ending. + const t = text.replace(/\r\n/g, '\n').replace(/\r/g, '\n'); + if (!t.startsWith('---\n')) { + throw new Error('frontmatter must start with "---\\n" at the very top of the file'); + } + const end = t.indexOf('\n---\n', 4); + if (end < 0) { + throw new Error('frontmatter must be closed by a line containing only "---"'); + } + const body = t.slice(4, end); + const lines = body.split('\n'); + + // No `---` line is allowed inside the frontmatter body. The closing + // marker is on its own line; we already extracted everything before + // it, so an inner `---` (matched by /^\s*---\s*$/) would corrupt the + // parse and split the frontmatter into two pieces. + const innerClose = lines.findIndex((l) => /^\s*---\s*$/u.test(l)); + if (innerClose >= 0) { + throw new Error(`frontmatter contains an inner "---" line at line ${innerClose + 1} (would split the block)`); + } + + // YAML keys in this plugin: ASCII letters / digits / underscore, plus + // dot and hyphen for the version-suffixed change-log keys + // (e.g. `changes-from-v0.1.2`). Anything fancier should be quoted. + const KEY_RE = /^[A-Za-z_][A-Za-z0-9_.\-]*$/u; + + const out = { _keys: [] }; + let i = 0; + while (i < lines.length) { + const line = lines[i]; + if (line === '' || /^\s*#/u.test(line)) { i += 1; continue; } + const m = line.match(/^([A-Za-z_][A-Za-z0-9_.\-]*)\s*:\s*(.*)$/u); + if (!m) { throw new Error(`unparseable frontmatter line ${i + 1}: ${JSON.stringify(line)}`); } + const key = m[1]; + const rest = m[2]; + if (out._keys.includes(key)) { + throw new Error(`duplicate top-level key "${key}" in frontmatter (first seen earlier)`); + } + out._keys.push(key); + if (rest === '' || rest === '|' || rest === '>') { + // Block value (literal `|` or folded `>`) or nested mapping under this key. + // Read indented continuation lines until we hit a non-indented line or EOF. + const blockKind = rest; + const blockLines = []; + i += 1; + while (i < lines.length && (lines[i] === '' || /^\s+/u.test(lines[i]))) { + blockLines.push(lines[i].replace(/^\s{1,2}/u, '')); + i += 1; + } + if (blockKind === '') { + // Nested mapping: parse each `key: value` line. + const nested = {}; + for (const bl of blockLines) { + if (bl === '') continue; + const nm = bl.match(/^([A-Za-z_][A-Za-z0-9_.\-]*)\s*:\s*(.*)$/u); + if (!nm) { throw new Error(`unparseable nested mapping line under "${key}": ${JSON.stringify(bl)}`); } + if (nested._keys?.includes(nm[1])) { + throw new Error(`duplicate nested key "${nm[1]}" under "${key}"`); + } + if (!nested._keys) nested._keys = []; + nested._keys.push(nm[1]); + // Strip surrounding quotes from the scalar value. + let v = nm[2].trim(); + if ((v.startsWith('"') && v.endsWith('"')) || (v.startsWith("'") && v.endsWith("'"))) { + v = v.slice(1, -1); + } + nested[nm[1]] = v; + } + out[key] = nested; + } else { + // Literal / folded scalar: collapse to a single string. + out[key] = blockLines.join('\n').trim(); + } + } else { + // Scalar value: strip surrounding quotes. + let v = rest.trim(); + if ((v.startsWith('"') && v.endsWith('"')) || (v.startsWith("'") && v.endsWith("'"))) { + v = v.slice(1, -1); + } + out[key] = v; + i += 1; + } + } + // Allow callers to introspect the raw key list; strip it from the + // returned object so it does not pollute property-style access. + const keys = out._keys; + delete out._keys; + out.__keys = keys; + return out; +} + +// Strip a single backtick-delimited code block / inline. We use this only +// to count prose claims (placeholders, Codex-harness parameter names), +// not to interpret the code; the asserts are conservative. +function findInCodeFences(text, re) { + // Normalize line endings to LF (see parseFrontmatter for rationale). + // The function is currently unused by the round-5 test surface + // (extractCallBodies replaced it on 61ae6f4), but it is kept as a + // public helper for any future round and must therefore be CRLF-safe + // to avoid silently returning 0 hits on Windows-checked-out files. + const t = text.replace(/\r\n/g, '\n').replace(/\r/g, '\n'); + const hits = []; + const fenceRe = /```[a-zA-Z0-9_-]*\n([\s\S]*?)```/gu; + for (const m of t.matchAll(fenceRe)) { + const block = m[1]; + let mm; + const local = new RegExp(re.source, re.flags.includes('g') ? re.flags : re.flags + 'g'); + while ((mm = local.exec(block)) !== null) { + hits.push({ match: mm[0], line: block.slice(0, mm.index).split('\n').length }); + } + } + return hits; +} + +// Extract every FULL `fnName(...)` call from every code block in `text`, +// matching paren-balanced bodies (multi-line allowed). Returns +// `{ match, line }` for each call where `match` is the entire +// `fnName(args...)` substring and `line` is the 1-based line within +// the code block where the call starts. +// +// The earlier `findInCodeFences(text, /task\s*\(/u)` only returned the +// first 5 characters of the call ('task('), so the parameter-name +// asserts that ran against it were vacuously true (you cannot find +// 'agent_name=' inside 'task('). This balanced-paren extractor closes +// the false-green hole the round-4 review identified. +// +// Multi-line calls (most real `task(` / `bash(` examples are multi-line) +// are supported: the paren walker does not break on newline, only on EOF. +function extractCallBodies(text, fnName) { + // Normalize line endings to LF (see parseFrontmatter for rationale). + // Without this, on Windows the fenceRe below would not match code + // fences opened with ` ```lang\r\n` (CRLF after the lang tag), so + // every `task(...)` / `bash(...)` example in the Skills' Windows- + // checked-out files would be invisible to the static check. Same + // hole the round-5 reviewer flagged for parseFrontmatter. + const t = text.replace(/\r\n/g, '\n').replace(/\r/g, '\n'); + const calls = []; + const nameRe = fnName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); + // Use a negative lookbehind so we match 'task(' at start-of-string or + // after any non-word char (whitespace, `>`, `(`, `,`, newline). The + // simpler `(^|[^\w])` form also captures the preceding char which + // throws off the callStart index. + const callRe = new RegExp(`(? 0) { + const ch = block[i]; + if (inString) { + if (ch === '\\') { i += 2; lineStart = false; continue; } + if (ch === inString) inString = null; + } else if (ch === '"' || ch === "'") { + inString = ch; + } else if (ch === '(') { + depth += 1; + } else if (ch === ')') { + depth -= 1; + } + // newline is allowed inside the call body; track it only for + // the line-number report. + if (ch === '\n') lineStart = true; + else if (ch !== ' ' && ch !== '\t' && ch !== '\r') lineStart = false; + i += 1; + } + if (depth === 0) { + const callStr = block.slice(m.index, i); + const line = block.slice(0, m.index).split('\n').length; + calls.push({ match: callStr, line }); + } + } + } + return calls; +} + +// --- Tests ------------------------------------------------------------------ + +// === Negative-first fixture tests (round-4 review close-out) === +// +// Each of these is constructed against a synthetic SKILL.md string that +// embeds a known-bad pattern. The assert demonstrates that the helper +// under test actually catches the pattern. To prove the test would +// fail on the previous (round-4) implementation, the comment at the +// top of each test names the change that would break it. + +const NEG_TASK = `--- +name: test-skill +description: | + A test fixture that embeds a Codex-style task() call. +license: Apache-2.0 +metadata: + author: antianqi + version: "0.0.1" +--- + +# Test + +The point of this fixture is to embed a \`task(agent_name="explore", brief="...")\` +call inside a code block. Any extractor that returns only the literal +\`task(\` (5 chars) cannot see the body and cannot fail this fixture. + +\`\`\`text +> task( + agent_name="explore", + brief="Investigate X", + description="d" + ) +\`\`\` +`; + +test('extractCallBodies returns the full task(...) body (not just "task(")', () => { + // This is the round-4 false-green hole: findInCodeFences returned + // mm[0] (the regex match = 'task('), so the assert + // !/\bagent_name\s*=/u.test('task(') was always true and the + // contract check never saw the actual parameters. + const calls = extractCallBodies(NEG_TASK, 'task'); + assert.equal(calls.length, 1, `expected 1 task(...) call, got ${calls.length}`); + // The body must contain the parameter names that appear AFTER '(' + // in the fixture. 'task(' alone would fail all four asserts below. + assert.match(calls[0].match, /\bagent_name\s*=/u, + 'extractCallBodies must capture the full body, including the agent_name= arg (proves the regex does not stop at the open paren)'); + assert.match(calls[0].match, /\bbrief\s*=/u, + 'extractCallBodies must capture brief= (the parameter that comes after the open paren)'); + assert.match(calls[0].match, /\bdescription\s*=/u, + 'extractCallBodies must capture description= (a parameter from the end of the body)'); +}); + +test('extractCallBodies returns "bash(...)" with full body, not just "bash("', () => { + const NEG_BASH = ` +\`\`\`text +> bash( + command="echo hello", + run_in_background=true + ) +# returns { job_id: "job_x", pid: 12345, log: "..." } +\`\`\` +`; + const calls = extractCallBodies(NEG_BASH, 'bash'); + assert.equal(calls.length, 1); + assert.match(calls[0].match, /\bcommand\s*=/u, + 'extractCallBodies must capture the full bash body including the command= arg'); + assert.match(calls[0].match, /\brun_in_background\s*=\s*true/u, + 'extractCallBodies must capture run_in_background=true in the bash body'); +}); + +test('extractCallBodies does NOT report false positives in prose', () => { + // The helper must only look at code blocks; a prose mention of + // "task(subagent=...)" should NOT be flagged because that prose + // is documenting the round-1 defect, not using it. + const PROSE_ONLY = ` +This Skill previously used the Codex-style \`task(subagent=...)\` form +but the round-1 review required switching to the canonical mcode 0.2.4 +\`task(agent_name=...)\` form. See \`fork-context-decision/SKILL.md\` +for the corrected example. +`; + const calls = extractCallBodies(PROSE_ONLY, 'task'); + assert.equal(calls.length, 0, + `extractCallBodies must not match prose mentions of task(subagent=); got ${calls.length} false positive(s)`); +}); + +test('every body after the closing frontmatter has no stray "---" that could split a second block (round-1 defect shape)', () => { + // Round-1 reviewer finding on fork-context-decision: "two metadata + // blocks and a stray '---' inside the frontmatter, plus a duplicate + // '# Fork Context Decision' heading". The earlier parseFrontmatter + // check used indexOf('\n---\n', 4) which only found the FIRST close, + // so a second frontmatter-shaped block in the body was invisible. + // + // The fix is a structural check: walk the body and fail on any line + // that is exactly '---' (or matches the '---' close pattern) AFTER + // the first frontmatter close. That is the only way a second YAML + // document could begin. + const DUPLICATE_BLOCK = `--- +name: fork-context-decision +description: | + This Skill is about how much context to pass. +license: Apache-2.0 +metadata: + author: antianqi + version: "0.1.0" +--- +metadata: + author: HACKED_INJECT + version: "99.99.99" +--- + +# body +`; + const NEG = `--- +name: neg +description: | + This Skill is fine. +license: Apache-2.0 +metadata: + author: antianqi + version: "0.1.0" +--- + +# body + +a prose paragraph. + +--- + +# another H1 (no second frontmatter, but the stray '---' is still wrong) +`; + // Helper: find the first frontmatter close, then check the body. + const stray = (text) => { + const t = text.replace(/\r\n/g, '\n').replace(/\r/g, '\n'); + const end = t.indexOf('\n---\n', 4); + if (end < 0) return null; + const body = t.slice(end + 5); + // Find any line in the body that is exactly '---'. If found, + // return the line number (1-based, in the body). + const lines = body.split('\n'); + for (let i = 0; i < lines.length; i++) { + if (/^\s*---\s*$/u.test(lines[i])) return { line: i + 1, content: lines[i] }; + } + return null; + }; + assert.ok(stray(DUPLICATE_BLOCK) !== null, + 'a duplicate-block fixture must be detected (the round-1 defect shape)'); + assert.ok(stray(NEG) !== null, + 'a stray "---" line in prose must also be detected (a future regression shape)'); + // Positive case: a clean body must NOT have any stray '---' line. + const CLEAN = `--- +name: clean +description: | + A clean Skill. +license: Apache-2.0 +metadata: + author: antianqi + version: "0.1.0" +--- + +# body +no stray dashes here. +`; + assert.equal(stray(CLEAN), null, 'a clean body must have no stray "---"'); +}); + +// === Existing 23-Skill checks (now using extractCallBodies) === + +test('all 23 SKILL.md files exist (one per directory under skills/)', () => { + const skills = listSkillFiles(); + assert.equal(skills.length, 23, `expected 23 Skills under ${SKILLS_ROOT}, found ${skills.length}: ${skills.map(s => s.name).join(', ')}`); +}); + +for (const { name, path } of listSkillFiles()) { + test(`SKILL.md for ${name} has exactly one valid frontmatter block`, () => { + const text = readFileSync(path, 'utf8'); + const fm = parseFrontmatter(text); + + // Required top-level fields. + assert.equal(fm.name, name, `${name}: frontmatter "name" must equal the directory name`); + assert.equal(typeof fm.description, 'string', `${name}: frontmatter "description" is required`); + assert.ok(fm.description.length > 0, `${name}: frontmatter "description" must be non-empty`); + assert.ok(fm.description.length <= 1024, `${name}: frontmatter "description" must be at most 1024 characters (got ${fm.description.length})`); + assert.equal(fm.license, 'Apache-2.0', `${name}: frontmatter "license" must be Apache-2.0`); + + // Required metadata block (every Skill in this plugin has it). + assert.ok(fm.metadata && typeof fm.metadata === 'object', `${name}: frontmatter "metadata" block is required`); + assert.equal(fm.metadata.author, 'antianqi', `${name}: metadata.author must be "antianqi"`); + assert.ok(typeof fm.metadata.version === 'string' && fm.metadata.version.length > 0, + `${name}: metadata.version is required and must be non-empty`); + + // Body after the closing `---` must be non-empty. + const body = text.slice(text.indexOf('\n---\n', 4) + 5).trim(); + assert.ok(body.length > 0, `${name}: instructions are required after the frontmatter`); + }); +} + +test('no SKILL.md contains a duplicate `author:` or `version:` key anywhere', () => { + // parseFrontmatter already rejects duplicate top-level / nested keys, but + // a body that *also* repeats `author:` outside the frontmatter would slip + // through. Pin both layers. + for (const { name, path } of listSkillFiles()) { + const text = readFileSync(path, 'utf8'); + // Strip the frontmatter so we only check the body. + const end = text.indexOf('\n---\n', 4); + const body = text.slice(end + 5); + // A body line of `author: ...` or `version: ...` at column 0 is the + // "duplicate author/version block" defect the reviewer flagged in the + // previous round (see `fork-context-decision` v0.1.x). Bullet-list / + // table / prose mentions like `- **author**: foo` or backtick-quoted + // `version:` are allowed and are not matched. + const dupAuthor = /(^|\n)author\s*:/u.test(body); + const dupVersion = /(^|\n)version\s*:/u.test(body); + assert.ok(!dupAuthor, `${name}: duplicate top-level "author:" key in body (forbidden — belongs only in metadata)`); + assert.ok(!dupVersion, `${name}: duplicate top-level "version:" key in body (forbidden — belongs only in metadata)`); + } +}); + +// --- mcode 0.2.4 tool-surface pinning --------------------------------------- + +// Skills that touch the `task` tool. The 5 Skills rewritten in this PR plus +// any future Skill that calls `task(` must use the canonical parameter +// names from `cli.js:B6c`: description, prompt, agent_name, run_in_background. +// The audit sweep after v1.0.4 also caught `error-recovery-strategy` which +// had a `task(subagent=...)` shape in its Example block; that fix is +// recorded as v0.1.2 of that Skill. The test below covers all Skills whose +// body contains a `task(` call in a code block — adding a new Skill that +// touches the `task` tool (or a new `task(` call in an existing Skill) +// will be caught by this test automatically. +const TASK_SKILLS = [ + 'fork-context-decision', + 'delegate-with-context', + 'parallel-fanout', + 'model-router', + 'background-task', + 'error-recovery-strategy', +]; + +test('TASK_SKILLS use canonical mcode 0.2.4 `task` parameter names (no `agent_name=`, no `brief=`, no `history=`, no `model_config_id=`)', () => { + for (const name of TASK_SKILLS) { + const path = join(SKILLS_ROOT, name, 'SKILL.md'); + const text = readFileSync(path, 'utf8'); + const calls = extractCallBodies(text, 'task'); + assert.ok(calls.length > 0, `${name}: expected at least one task(...) example in a code block`); + + for (const { match } of calls) { + // Inside every `task(` example, the parameters must be the canonical + // mcode 0.2.4 set. extractCallBodies returns the FULL paren-balanced + // body, so the regex below sees the actual arguments (round-4 fix: + // the previous findInCodeFences returned only 'task(' and the + // asserts were vacuously true). + // + // The banned list is the union of all Codex-harness parameter + // names that have appeared in the round-1 / round-2 / round-3 / + // round-4 review comments. Each is a known-bad shape that the + // mcode 0.2.4 `task` tool (cli.js:B6c strict validator) does + // not accept. `subagent_type=` is also banned because the + // canonical form is `agent_name=` (cli.js:j6c accepts the + // alias but the Skills prefer canonical). + assert.ok(!/\bsubagent_type\s*=/u.test(match), + `${name}: task(...) example uses "subagent_type="; mcode 0.2.4 canonical is "agent_name=" (subagent_type is accepted as a runtime alias but Skills prefer canonical)`); + assert.ok(!/\bsubagent\s*=/u.test(match), + `${name}: task(...) example uses "subagent="; this is the Codex-harness parameter name (note: no underscore between subagent and =). mcode 0.2.4 canonical is "agent_name=" (round-1 defect shape, was in parallel-fanout and delegate-with-context before v1.0.3)`); + assert.ok(!/\bbrief\s*=/u.test(match), + `${name}: task(...) example uses "brief="; mcode canonical is "prompt="`); + assert.ok(!/\bhistory\s*=/u.test(match), + `${name}: task(...) example uses "history="; mcode 0.2.4 has no context-sharing parameter (the 3 fork modes are expressed by what is inlined into "prompt")`); + assert.ok(!/\bmodel_config_id\s*=/u.test(match), + `${name}: task(...) example uses "model_config_id="; mcode 0.2.4 task tool does not expose a per-call model field (model selection is session-level)`); + assert.ok(!/\bfork_turns\s*=/u.test(match), + `${name}: task(...) example uses "fork_turns="; this is the Codex-harness parameter name, removed in v1.0.3`); + assert.ok(!/\bagent_type\s*=/u.test(match), + `${name}: task(...) example uses "agent_type="; mcode canonical is "subagent_type="`); + } + } +}); + +// Catch-all: any Skill whose code block contains a `task(` call but is +// not in the TASK_SKILLS allow-list must be added to the list (or the +// call removed). Without this, a future contributor could drop a +// `task(subagent=...)` shape into e.g. `plan-stream-emit` and slip +// past the static check. +test('every Skill with a `task(` call in a code block is in the TASK_SKILLS allow-list', () => { + const allow = new Set(TASK_SKILLS); + const offenders = []; + for (const { name, path } of listSkillFiles()) { + const text = readFileSync(path, 'utf8'); + // Use the full-body extractor so a Skill that hides a `task(` + // call in the middle of a long body is still detected. + const calls = extractCallBodies(text, 'task'); + if (calls.length === 0) continue; + if (!allow.has(name)) offenders.push(name); + } + assert.deepEqual(offenders, [], + `Skills with a task(...) call but not in the static-check allow-list: ${offenders.join(', ')}. Either add them to TASK_SKILLS (and pin their parameter names) or remove the task(...) call.`); +}); + +// === Round-4 #3: sub-agent manifest path verification === +// +// Reviewer finding: "fork-context-decision/SKILL.md 仍声称三个 sub-agent +// manifest 位于 assets/agents//agent.md;请用当前 MiniMax Code 可验证 +// 契约确认该路径". The Skills name explore / worker / verifier as +// agent_name values; we observed in our own mcode 0.2.4 install that +// each named sub-agent has a per-agent manifest at +// `assets/agents//agent.md` (the path is **not** part of the +// public runtime contract — it is a dev-machine-internal layout that +// varies across installs and platforms; user-facing Skills no longer +// reference it). This test is a dev-only best-effort verification +// from the active mcode install; it is skipped if no mcode is +// reachable. The Skills themselves use the canonical mcode 0.2.4 +// `agent_name=` form; this test fails closed if any Skill body uses +// the legacy `subagent_type=` form, OR if a Skill claims a value +// whose on-disk manifest is missing in our own install. +test('sub-agent types claimed in Skills are present in the local mcode 0.2.4 install (dev-only check; skipped if mcode is unreachable)', () => { + // Locate the mcode install. The active mcode is at $env:LOCALAPPDATA + // typically; fall back to a well-known absolute path. Skip the test + // (not fail it) if no mcode install is reachable, so this test is + // hermetic on dev machines that don't have mcode installed. + const candidates = [ + join(process.env.LOCALAPPDATA || '', 'npm', 'node_modules', '@minimax-ai', 'code'), + join(process.env.APPDATA || '', 'npm', 'node_modules', '@minimax-ai', 'code'), + 'C:\\Users\\Administrator\\.minimax-code\\node_modules\\@minimax-ai\\code', + ]; + const mcodeRoot = candidates.find((p) => p && existsSync(join(p, 'assets', 'agents'))); + if (!mcodeRoot) { + // No mcode reachable on this machine. Skip rather than fail. + return; + } + // Scan all 23 Skills; collect every distinct agent_name value that + // appears in a `task(... agent_name="X" ...)` example. The values + // must come from the canonical mcode 0.2.4 set. + const claimed = new Set(); + const reSub = /\bagent_name\s*=\s*["']([a-z0-9_-]+)["']/giu; + for (const { path } of listSkillFiles()) { + const text = readFileSync(path, 'utf8'); + for (const c of extractCallBodies(text, 'task')) { + for (const m of c.match.matchAll(reSub)) claimed.add(m[1]); + } + } + // The canonical sub-agent types (verified from the local mcode + // install's `assets/agents/{explore,worker,verifier}/agent.md` + // layout). `mavis` is the root agent and MUST NOT be a claimed + // agent_name. + assert.ok(!claimed.has('mavis'), + 'mavis is the root agent, not an agent_name; some Skill is still claiming it. Found in: ' + Array.from(claimed).join(', ')); + for (const name of claimed) { + if (name === 'mavis') continue; // asserted above + const manifest = join(mcodeRoot, 'assets', 'agents', name, 'agent.md'); + assert.ok(existsSync(manifest), + `agent_name "${name}" is claimed in a Skill example but the manifest ${manifest} does not exist on disk. Either the Skill is wrong or the mcode install is wrong.`); + } +}); + +test('background-task uses `task(run_in_background: true)` + `task_query` / `task_output` / `task_stop` (no `bash(task_name=...)`, no `bash(action="kill")`)', () => { + const path = join(SKILLS_ROOT, 'background-task', 'SKILL.md'); + const text = readFileSync(path, 'utf8'); + + // Sub-agent background uses the `task` tool with `run_in_background: true`. + // extractCallBodies returns the FULL paren-balanced body so the + // run_in_background check actually sees the body (round-4 fix). + const taskCalls = extractCallBodies(text, 'task'); + assert.ok(taskCalls.length > 0, + 'background-task must show at least one task(...) example (inside a code block)'); + assert.ok(taskCalls.some((c) => /\brun_in_background\s*[:=]\s*true/u.test(c.match)), + 'background-task must show run_in_background: true / = true inside a task(...) call'); + assert.ok(extractCallBodies(text, 'task_query').length > 0, + 'background-task must show task_query(...) for listing / fetching a background task'); + assert.ok(extractCallBodies(text, 'task_output').length > 0, + 'background-task must show task_output(task_id=...) for reading output'); + assert.ok(extractCallBodies(text, 'task_stop').length > 0, + 'background-task must show task_stop(task_id=...) for stopping a background task'); + + // Shell background uses the `bash` tool with `run_in_background: true. + // The call must carry run_in_background=true AND a documented return + // shape (job id / pid / log path) somewhere in the same code block + // (round-4 finding: the return shape was prose-only, not test-pinned). + const bashCalls = extractCallBodies(text, 'bash'); + assert.ok(bashCalls.length > 0, + 'background-task must show at least one bash(...) example (inside a code block)'); + const bgBashCalls = bashCalls.filter((c) => /\brun_in_background\s*[:=]\s*true/u.test(c.match)); + assert.ok(bgBashCalls.length > 0, + 'background-task must show at least one bash(... run_in_background: true) example'); + // For each background-bash call, the call body must include a + // documented handle. We extract the whole fenced block the call + // appears in and assert the block mentions a handle keyword. + // Normalize line endings first; the call bodies above are taken from + // the normalized text, so the search has to be too. + const textNorm = text.replace(/\r\n/g, '\n').replace(/\r/g, '\n'); + for (const call of bgBashCalls) { + const fenceRe = /```[a-zA-Z0-9_-]*\n([\s\S]*?)```/gu; + let hostBlock = null; + for (const fm of textNorm.matchAll(fenceRe)) { + if (fm[1].includes(call.match)) { hostBlock = fm[1]; break; } + } + assert.ok(hostBlock !== null, + `background-task has a bash(... run_in_background: true) call but its code block could not be located: ${call.match.slice(0, 80)}`); + const hasHandle = /\b(job_?id|pid|log_?path|log\b|handle)\b/iu.test(hostBlock); + assert.ok(hasHandle, + `background-task bash(... run_in_background: true) example in code block must document the return handle (job id / pid / log path). Block:\n${hostBlock}`); + } + + // Codex-harness placeholders that do NOT exist on mcode 0.2.4. We only + // look inside code blocks; prose mentions like "removed bash(task_name=...)" + // in the frontmatter change-log are allowed (they are explicitly removing + // the bad pattern, not using it). The call body is the FULL paren-balanced + // body now (round-4 fix), so this assert actually sees the args. + for (const call of bashCalls) { + assert.ok(!/\btask_name\s*=/u.test(call.match), + 'background-task code block uses "bash(... task_name=...)" — mcode 0.2.4 bash has no task_name field; rewrite as `bash(command=..., run_in_background=true)`'); + assert.ok(!/\baction\s*=\s*["']kill["']/u.test(call.match), + 'background-task code block uses "bash(... action=\"kill\")" — mcode 0.2.4 bash has no action sub-action; killing is via task_stop or the host job-control API'); + } +});