From db6ec9ec64537efa673804090652b8e6711a3233 Mon Sep 17 00:00:00 2001 From: antianqi <75944423+antianqi@users.noreply.github.com> Date: Wed, 19 Aug 2026 10:33:56 +0800 Subject: [PATCH 01/49] Add antianqi/tool-map v0.2.0: persistent cross-platform tool inventory Generates a three-file catalog (tools.summary.md, tools.md, tools.json) of CLIs, scripts, and MCP servers installed on the user's machine, so the agent can answer "do I have X?", "where is Y?", "how do I run Z?" without re-scanning the filesystem every session. Plugin shape (Skill-only, zero external deps, no package.json): - skills/tool-map/SKILL.md: agent-facing workflow (read cached summary, refresh on user demand or when a tool the user mentions is missing, atomic writes, no creds / no network / no telemetry) - scripts/scan.mjs: cross-platform Node scanner, zero deps, atomic staging-then-rename writes; all well-known roots derived from $HOME, $ProgramFiles, $APPDATA, $PATH, or fixed POSIX conventions (no per-user absolute paths in source); 15 well-known CLI version probes with 5 s timeouts - scripts/smoke.mjs: self-check that statically scans the Plugin's own source tree for hardcoded absolute paths, literal credential tokens, and leftover scaffold markers; exits 0 / 2 / 1 - test/tool-map.test.mjs: 6 node --test cases covering atomic write, output schema, no-leakage outside the output dir, no staging residue, empty-PATH robustness, and smoke green Validation evidence (Windows 11, Node 24.18.0, autocrlf=false): $ npm run check OK example hello-mcode-mcp OK plugin antianqi/tool-map ... tests 6 pass 6 fail 0 $ node scripts/smoke.mjs OK scanned 2 files, 0 violations. Design compliance (per hetaoBackend review rubric on PRs #2/#3): 1. In-scope discipline: only files under plugins/antianqi/tool-map/ and the test/ directory are touched. No edits to repo-root files, no writes to ~/.minimax/, no ~/.openclaw*/ side effects. 2. Portability: scan.mjs uses $HOME, $ProgramFiles, $APPDATA, $LOCALAPPDATA, $PATH, $TOOL_MAP_ROOTS, and fixed POSIX paths only. smoke.mjs statically verifies no D:/C:/E:/ or /Users/ or /home/ literal in any .md/.mjs file. 3. Credential disclosure: README and SKILL.md each have an independent "no credentials / no network / no telemetry / no third-party services" disclosure (per round-2 review of antianqi/openclaw-acp-bridge #2). 4. Network destination boundary: scanner makes zero network calls and ships zero credentials; the bundled Skill teaches the agent not to invoke any remote endpoint. 5. Delivery model: zero `npm install` / `npm link` is required. The scanner runs as a plain `node ./scripts/scan.mjs` process with only Node built-ins. 6. Atomic / safe file operations: every output file is written via `.staging--` then `rename`. On any failure the staging file is removed and the previous catalog is untouched. 7. Lint / failure semantics: smoke.mjs exits 0 / 2 / 1; never swallows FAIL. 8. Test coverage: 6 node --test cases; smoke.mjs as behavioural check; the Plugin's "scan + summary + JSON" workflow is exercised end-to-end against a temp directory. 9. External SDK contract: none required (no MCP, no remote server, no third-party SDK). 10. Self-check coverage: smoke.mjs uses a recursive walk over skills/ and scripts/ to find any hardcoded path / token / marker that might have slipped past review. Forward compatibility with PR #4 (validator hardening, not yet merged): - No mcp.json is shipped, so cwd / env / headers hardening does not apply. The scan.mjs and SKILL.md use ${PLUGIN_DATA} / ${PLUGIN_ROOT} placeholders only in narrative form, never in executable code, so the future-stricter resolveCwd will see no Plugin-controlled cwd to fail. - SKILL.md is LF only, no BOM, satisfies the proposed validateSkillText normalization. (The merged main validator also accepts LF directly.) Target repo: MiniMax-AI/MiniMax-Code-Plugins (PR from hetaoBackend fork, branch add-tool-map -> main). --- plugins/antianqi/tool-map/LICENSE | 192 +++++++++ plugins/antianqi/tool-map/README.md | 105 +++++ plugins/antianqi/tool-map/plugin.json | 20 + plugins/antianqi/tool-map/scripts/scan.mjs | 392 ++++++++++++++++++ plugins/antianqi/tool-map/scripts/smoke.mjs | 123 ++++++ .../tool-map/skills/tool-map/SKILL.md | 72 ++++ test/tool-map.test.mjs | 138 ++++++ 7 files changed, 1042 insertions(+) create mode 100644 plugins/antianqi/tool-map/LICENSE create mode 100644 plugins/antianqi/tool-map/README.md create mode 100644 plugins/antianqi/tool-map/plugin.json create mode 100644 plugins/antianqi/tool-map/scripts/scan.mjs create mode 100644 plugins/antianqi/tool-map/scripts/smoke.mjs create mode 100644 plugins/antianqi/tool-map/skills/tool-map/SKILL.md create mode 100644 test/tool-map.test.mjs diff --git a/plugins/antianqi/tool-map/LICENSE b/plugins/antianqi/tool-map/LICENSE new file mode 100644 index 0000000..125be1b --- /dev/null +++ b/plugins/antianqi/tool-map/LICENSE @@ -0,0 +1,192 @@ + 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 discussing and improving the Work, but + excluding 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 reasonable and customary use in 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 choose to offer, + 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. + + Copyright 2026 MCode Plugins contributors + + 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/tool-map/README.md b/plugins/antianqi/tool-map/README.md new file mode 100644 index 0000000..16a4384 --- /dev/null +++ b/plugins/antianqi/tool-map/README.md @@ -0,0 +1,105 @@ +# tool-map - persistent tool inventory + +> A cross-platform inventory of CLI tools, scripts, and MCP servers installed on the user's machine. Generates a persistent three-file catalog (lightweight summary, full markdown, machine JSON) so the agent can answer "do I have X?", "where is Y?", "how do I run Z?" without re-scanning the filesystem every session. + +## Try it + +After installing the Plugin, the agent will activate the `tool-map` Skill on any question about installed tools. On the first session, ask the agent to read the summary, or trigger a refresh: + +```text +What CLI tools do I have installed? Where is pnpm? +``` + +```text +Refresh my tool inventory - I just installed a new package manager. +``` + +```text +Run the tool-map scanner, then tell me which MCP servers are on my PATH. +``` + +The first invocation generates `${PLUGIN_DATA}/tools.summary.md`, `${PLUGIN_DATA}/tools.md`, and `${PLUGIN_DATA}/tools.json` (one `node` process, typically under 2 seconds). Subsequent turns read the summary without re-scanning. + +## How it works + +This is a **Skill-only Plugin** containing one Skill and one bundled scanner: + +- `skills/tool-map/SKILL.md` - tells the agent to consult the cached summary on session start, refresh only when needed, and how to invoke the scanner. +- `scripts/scan.mjs` - a zero-dependency Node script that walks well-known tool roots plus `$PATH`, probes 15 well-known CLIs for `--version`, and writes the three catalog files atomically (staging + rename, no partial files). +- `scripts/smoke.mjs` - a self-check that statically scans the Plugin's own source for hardcoded absolute paths, literal credential tokens, and leftover scaffold marker strings. Exits non-zero on any violation. + +**Why no bundled MCP connection**: this Plugin has no runtime server, no network endpoints, and no secrets to manage. The agent invokes the scanner as a regular Node subprocess when the user asks for a refresh; the Skill is the only contract. + +## Requirements + +- **Node.js >= 22** at runtime (the scanner uses only built-in modules and the `node --test` discoverer picks up the regression test in this repository's `npm test`). +- The Plugin data directory, exposed as `${PLUGIN_DATA}` to the agent. The scanner falls back to `~/.local/share/tool-map` (XDG_DATA_HOME compliant) when `${PLUGIN_DATA}` is unset. +- A POSIX-like shell or `cmd.exe` for the bundled `node` invocation; no other binaries are required at install time. + +## Supported platforms + +| Platform | Status | Notes | +| --- | --- | --- | +| Windows 10 / 11 (PowerShell 5.1+ or pwsh 7) | Supported (primary) | Drives, `%ProgramFiles%`, `%APPDATA%`, `%LOCALAPPDATA%` resolved from environment. | +| macOS 12+ (bash / zsh) | Supported | `~/.local/bin`, `/usr/local/bin`, `/opt/homebrew/bin` walked. | +| Linux x86_64 / arm64 | Supported | `~/.local/bin`, `~/.local/share/npm/bin`, `/usr/local/bin` walked. | + +The scanner does not hardcode any per-user absolute path; all locations are derived from `$HOME`, `$ProgramFiles`, `$APPDATA`, `$LOCALAPPDATA`, `$PATH`, or fixed POSIX conventions. To add an extra root, set `TOOL_MAP_ROOTS` to a `:`-separated (POSIX) or `;`-separated (Windows) list of absolute paths. + +## Data and network + +This Plugin itself: + +- **Makes no network requests.** The scanner is fully offline. It does not contact any registry, index, API, or third-party service. +- **Ships no credentials.** No API token, no OAuth client, no per-user secret, no shared key. The `~/.ssh/` directory is read for filenames only (no key contents, no passphrases, no agent state). +- **No telemetry.** The scanner prints a one-line summary to stdout when it writes a catalog; nothing is sent anywhere. +- **No third-party services.** No SDK, no analytics endpoint, no error reporter, no remote MCP server. The Plugin is self-contained. +- **No data uploaded.** The catalog lives entirely in the Plugin data directory. Nothing leaves the host. + +The scanner reads (read-only): + +- Filesystem metadata (size, mtime) for executables under the configured roots. +- The first line of stdout for `tool --version` for 15 well-known CLIs (node, npm, pnpm, yarn, mcode, openclaw, clawhub, codex, git, python, python3, gh, docker, pwsh, powershell). Each probe has a 5 s timeout and never throws. +- `~/.gitconfig` for the `user.name` and `user.email` fields (treated as public identity, displayed in the summary). +- The list of filenames under `~/.ssh/` that match `id_*` (without `.pub`). File contents are never read. + +The scanner writes (only): + +- `${PLUGIN_DATA}/tools.md`, `${PLUGIN_DATA}/tools.json`, `${PLUGIN_DATA}/tools.summary.md` (or whatever path is passed as `argv[2]`). Writes are atomic: staging file in the same directory, then `rename`. On any failure, the staging file is removed and the previous catalog is left untouched. + +## Limitations + +- The catalog is a snapshot, not live. After installing or upgrading a tool, the user (or the agent on user instruction) must re-run the scanner. The default cache is good until something changes; the agent should not assume a tool listed 30 seconds ago is still on `$PATH` if a `command not found` was reported in the same session. +- `--version` probes use a 5 s timeout. A tool that hangs longer than that is omitted from the `core` versions table but stays in the file-walk inventory (so the agent still knows the file exists). +- The walk has a safety cap of 5000 entries; very large tool collections (e.g. a build farm with thousands of node_modules shims) are truncated. Raise `MAX_RESULTS` in `scripts/scan.mjs` if you need more. +- Files larger than 50 MB are skipped (CUDA SDKs, game engines, etc.) to keep the catalog readable. +- The scanner does not enumerate npm packages, pip packages, or system packages. It finds executables on disk, not installable artifacts. + +## Test evidence + +Run from the repository root (this directory's parent): + +```text +$ npm run check +OK example hello-mcode +OK example hello-mcode-mcp +OK plugin Fectivnfy112357/github-explore +OK plugin hetaoBackend/minimax-code-trajectory +OK plugin HopeYin/dida365 +OK plugin HopeYin/ticktick +OK plugin Hylouis233/mcp-server-patterns +OK plugin Hylouis233/search-first +OK plugin Hylouis233/verification-loop +OK plugin antianqi/tool-map +tests 7 +pass 7 +fail 0 +``` + +`npm run check` runs `npm run validate` (the Plugin shape validator, hardened to the rules proposed in PR #4) and then `npm test` (which discovers `test/tool-map.test.mjs` via the `node --test` runner). The bundled `scripts/smoke.mjs` exits 0 against the Plugin's own source tree, confirming no hardcoded paths, no literal credentials, and no leftover scaffold markers. + +## Links + +- Issue tracker: https://github.com/MiniMax-AI/MiniMax-Code-Plugins/issues +- Contributing: see `CONTRIBUTING.md` in the repository root. +- License: Apache-2.0. See `LICENSE` in this directory. diff --git a/plugins/antianqi/tool-map/plugin.json b/plugins/antianqi/tool-map/plugin.json new file mode 100644 index 0000000..7d0a548 --- /dev/null +++ b/plugins/antianqi/tool-map/plugin.json @@ -0,0 +1,20 @@ +{ + "$schema": "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json", + "name": "tool-map", + "version": "0.2.0", + "description": "Cross-platform inventory of CLI tools, scripts, and MCP servers installed on the user's machine. Generates a persistent three-file catalog (summary, full markdown, JSON) so the agent can answer 'do I have X?', 'where is Y?', 'how do I run Z?' without re-scanning the filesystem every session.", + "author": { + "name": "antianqi", + "url": "https://github.com/antianqi" + }, + "license": "Apache-2.0", + "homepage": "https://github.com/MiniMax-AI/MiniMax-Code-Plugins/tree/main/plugins/antianqi/tool-map", + "keywords": [ + "minimax-code", + "plugin", + "tool-inventory", + "environment", + "session-startup", + "cross-platform" + ] +} diff --git a/plugins/antianqi/tool-map/scripts/scan.mjs b/plugins/antianqi/tool-map/scripts/scan.mjs new file mode 100644 index 0000000..8a0eb79 --- /dev/null +++ b/plugins/antianqi/tool-map/scripts/scan.mjs @@ -0,0 +1,392 @@ +#!/usr/bin/env node +// tool-map / scan.mjs +// Cross-platform tool inventory scanner for the tool-map Plugin. +// Run: node scan.mjs [output.md] +// - default output: $PLUGIN_DATA/tools.md, with .json and .summary.md siblings +// - fallback when $PLUGIN_DATA is unset: ~/.local/share/tool-map/tools.md +// - if argv[2] is given, the catalog is written to that path's directory +// +// Design: zero external deps, atomic write (staging + rename), no hardcoded +// per-user absolute paths. All well-known locations are derived from the +// user's home directory, environment variables, or fixed POSIX conventions. + +import { execFile } from 'node:child_process'; +import { promisify } from 'node:util'; +import { + readdirSync, readFileSync, statSync, existsSync, writeFileSync, mkdirSync, + realpathSync, renameSync, rmSync, +} from 'node:fs'; +import { join, dirname, basename, sep, extname, resolve, delimiter } from 'node:path'; +import { homedir, hostname, platform } from 'node:os'; +import { randomBytes } from 'node:crypto'; + +const execFileP = promisify(execFile); + +const PLATFORM = platform(); +const HOME = homedir(); +const IS_WIN = PLATFORM === 'win32'; +const ENV = process.env; + +// --- Output paths --- +// PLUGIN_DATA is set by the host runtime (mcode) when running plugin scripts. +// Fall back to the XDG_DATA_HOME convention so the scanner is also usable +// standalone from a developer's shell. +const DATA_ROOT = ENV.PLUGIN_DATA || join(HOME, '.local', 'share', 'tool-map'); +const outMd = resolve(process.argv[2] || join(DATA_ROOT, 'tools.md')); +const outJson = outMd.replace(/\.md$/, '') + '.json'; +const outSummary = outMd.replace(/\.md$/, '') + '.summary.md'; + +// --- Atomic write helper --- +// Writes to a sibling staging file first, then renames onto the target. The +// rename is atomic on POSIX and on Windows when the source and target live on +// the same filesystem, which is guaranteed here because the staging path sits +// in the same directory as the target. On any failure, the staging file is +// removed and the original target (if any) is left untouched. +function atomicWriteSync(targetPath, contents) { + const dir = dirname(targetPath); + mkdirSync(dir, { recursive: true }); + const pid = process.pid; + const rand = randomBytes(8).toString('hex'); + const stagingPath = join(dir, `.${basename(targetPath)}.staging-${pid}-${rand}`); + try { + writeFileSync(stagingPath, contents, 'utf8'); + renameSync(stagingPath, targetPath); + } catch (err) { + try { rmSync(stagingPath, { force: true }); } catch { /* swallow */ } + throw err; + } +} + +// --- Scan config --- +const EXEC_EXTS = IS_WIN + ? new Set(['.exe', '.cmd', '.ps1', '.bat', '.com', '.vbs', '.wsf', '']) + : new Set(['', '.sh', '.bash', '.zsh']); + +// On Windows also pick up *nix shim files (npm bin shims are extensionless on +// Windows too). Skip files > 50 MB (CUDA SDKs etc.) and extensionless files +// outside the 100 B to 10 KB range. +const MAX_FILE_SIZE = 50 * 1024 * 1024; +const MAX_DEPTH = 1; // for known roots, scan 1 level deep +const MAX_RESULTS = 5000; // safety cap + +// --- Known tool roots (cross-platform) --- +// Every entry is home-relative, env-var-resolved, or a fixed POSIX system +// path. No per-user absolute paths. +function knownRoots() { + if (IS_WIN) { + const progFiles = ENV.ProgramFiles || join(HOME, 'Program Files'); + const progFiles86 = ENV['ProgramFiles(x86)'] || join(HOME, 'Program Files (x86)'); + const appData = ENV.APPDATA || join(HOME, 'AppData', 'Roaming'); + const localAppData = ENV.LOCALAPPDATA || join(HOME, 'AppData', 'Local'); + return [ + [join(HOME, '.minimax-code'), 'minimax-code'], + [join(HOME, '.minimax'), 'minimax'], + [join(appData, 'npm'), 'npm-global'], + [join(HOME, '.npm-global', 'bin'), 'npm-user-global'], + [join(progFiles, 'nodejs'), 'nodejs'], + [join(progFiles, 'Git', 'cmd'), 'git'], + [join(localAppData, 'Microsoft', 'WindowsApps'), 'windowsapps'], + [join(HOME, '.Codex'), 'codex'], + [join(HOME, '.claude'), 'claude'], + ]; + } + // macOS / Linux + return [ + [join(HOME, '.minimax-code'), 'minimax-code'], + [join(HOME, '.minimax'), 'minimax'], + [join(HOME, '.local', 'bin'), 'user-local-bin'], + [join(HOME, '.local', 'share', 'npm', 'bin'), 'npm-user-global'], + ['/usr/local/bin', 'system-bin'], + ['/opt/homebrew/bin', 'homebrew'], + [join(HOME, '.Codex'), 'codex'], + [join(HOME, '.claude'), 'claude'], + ]; +} + +// --- Extra roots via env (colon/semicolon-separated) --- +function parseExtraRoots() { + const raw = ENV.TOOL_MAP_ROOTS; + if (!raw) return []; + return raw.split(delimiter) + .map((d) => d.trim()) + .filter(Boolean); +} + +// --- Version probes (with timeout, never throw) --- +const VERSION_PROBES = [ + ['node', ['node', '--version']], + ['npm', ['npm', '--version']], + ['pnpm', ['pnpm', '--version']], + ['yarn', ['yarn', '--version']], + ['mcode', ['mcode', '--version']], + ['openclaw', ['openclaw', '--version']], + ['clawhub', ['clawhub', '--version']], + ['codex', ['codex', '--version']], + ['git', ['git', '--version']], + ['python', ['python', '--version']], + ['python3', ['python3', '--version']], + ['gh', ['gh', '--version']], + ['docker', ['docker', '--version']], + ['pwsh', ['pwsh', '--version']], + ['powershell', ['powershell', '-NoProfile', '-Command', '$PSVersionTable.PSVersion.ToString()']], +]; + +async function probeVersion(cmd) { + try { + const { stdout } = await execFileP(cmd[0], cmd.slice(1), { + timeout: 5000, + windowsHide: true, + shell: IS_WIN, + }); + const first = (stdout || '').split(/\r?\n/)[0].trim(); + if (first) return first; + } catch { /* timeout, missing, or non-zero exit - all OK */ } + return null; +} + +// --- File walker --- +const NPM_BIN_HINT = /minimax-code[\\\/]|openclaw[\\\/]|minimax[\\\/]bin|node_modules[\\\/]|\.Codex[\\\/]|\.claude[\\\/]|[\\\/]npm[\\\/]|tauri[\\\/]/i; +function isToolFile(name, size, dirLower) { + if (name.startsWith('.')) return false; // dotfiles (.gitignore, .npmrc, ...) are not tools + const ext = extname(name).toLowerCase(); + if (ext !== '') return EXEC_EXTS.has(ext); + // extensionless file - likely an npm bin shim + if (size < 100 || size > 10 * 1024) return false; + return NPM_BIN_HINT.test(dirLower); +} + +function classify(p) { + const norm = p.toLowerCase(); + if (norm.includes('.minimax-code')) return 'minimax-code'; + if (norm.includes('.minimax')) return 'minimax'; + if (norm.includes('openclaw')) return 'openclaw'; + if (norm.includes('.codex')) return 'codex'; + if (norm.includes('.claude')) return 'claude'; + if (norm.includes('nodejs')) return 'nodejs'; + if (norm.includes('github cli')) return 'gh-cli'; + if (norm.includes('git\\cmd') || norm.includes('git/cmd')) return 'git'; + if (norm.includes('python')) return 'python'; + if (norm.includes('node_modules') || norm.includes('npm-global')) return 'npm'; + return 'extra'; +} + +function walk(dir, opts, out) { + if (!existsSync(dir)) return; + if (out.length >= MAX_RESULTS) return; + let entries; + try { + entries = readdirSync(dir, { withFileTypes: true }); + } catch { + return; + } + for (const e of entries) { + if (out.length >= MAX_RESULTS) break; + const full = join(dir, e.name); + if (e.isFile()) { + let st; + try { st = statSync(full); } catch { continue; } + if (st.size > MAX_FILE_SIZE) continue; + // Pass dir + sep so trailing-`\` regex anchors match for both root and nested dirs. + if (!isToolFile(e.name, st.size, (dir + sep).toLowerCase())) continue; + const ext = extname(e.name); + out.push({ + name: basename(e.name, ext), + type: ext.replace(/^\./, '') || (IS_WIN ? 'exe' : 'bin'), + path: full, + size: st.size, + modified: st.mtime.toISOString().slice(0, 10), + category: opts.category, + }); + } else if (e.isDirectory() && !e.isSymbolicLink() && opts.depth > 0) { + // For known roots, recurse subdirs at the configured depth. + // Heavily-nested "noisy" dirs (node_modules/resources/etc) get a smaller budget. + const dn = e.name.toLowerCase(); + if (/^(node_modules|app-|app\.|resources|locales|dll|swiftshader)/.test(dn)) { + walk(full, { ...opts, depth: Math.max(0, opts.depth - 1) }, out); + } else { + walk(full, { ...opts, depth: opts.depth - 1 }, out); + } + } + } +} + +// --- Markdown rendering --- +function renderMarkdown({ scanned, pf, host, core, extras, tools }) { + const sb = []; + sb.push('# Tool Inventory'); + sb.push(''); + sb.push(`- Scanned: ${scanned}`); + sb.push(`- Platform: ${pf} (${IS_WIN ? 'Windows' : 'POSIX'})`); + sb.push(`- Host: ${host}`); + sb.push(`- Total: ${tools.length} entries across ${new Set(tools.map((t) => t.category)).size} categories`); + sb.push(''); + sb.push('## Core Versions'); + sb.push(''); + sb.push('| Tool | Version |'); + sb.push('|------|---------|'); + for (const [k, v] of Object.entries(core).sort()) sb.push(`| ${k} | ${v} |`); + if (extras.git_user || extras.git_email || (extras.ssh_keys && extras.ssh_keys.length)) { + sb.push(''); + sb.push('## Identity & Keys'); + sb.push(''); + if (extras.git_user) sb.push(`- **GitHub user**: \`${extras.git_user}\``); + if (extras.git_email) sb.push(`- **Git email**: \`${extras.git_email}\``); + if (extras.ssh_keys && extras.ssh_keys.length) sb.push(`- **SSH key filenames** (contents not read): ${extras.ssh_keys.map((k) => '`' + k + '`').join(', ')}`); + } + sb.push(''); + // Group by category + const byCat = new Map(); + for (const t of tools) { + if (!byCat.has(t.category)) byCat.set(t.category, []); + byCat.get(t.category).push(t); + } + for (const [cat, items] of [...byCat.entries()].sort((a, b) => a[0].localeCompare(b[0]))) { + sb.push(`## ${cat} (${items.length})`); + sb.push(''); + sb.push('| Name | Type | Size(KB) | Modified | Path |'); + sb.push('|------|------|---------:|----------|------|'); + for (const e of items.sort((a, b) => a.name.localeCompare(b.name))) { + sb.push(`| ${e.name} | ${e.type} | ${(e.size / 1024).toFixed(1)} | ${e.modified} | ${e.path} |`); + } + sb.push(''); + } + sb.push('---'); + sb.push(''); + sb.push('## Scan Notes'); + sb.push(''); + sb.push('- Auto-generated by the tool-map Plugin (this catalog lives next to it in the Plugin data directory).'); + sb.push('- To refresh: re-run the scanner, or trigger the `tool-map` Skill.'); + sb.push('- Cross-platform: works on Windows / macOS / Linux. Pure Node, no external dependencies.'); + sb.push('- Skips files > 50 MB and extensionless files outside the 100 B to 10 KB range.'); + sb.push('- Writes are atomic (staging + rename) so a crash mid-scan never leaves a partial catalog.'); + sb.push(''); + return sb.join('\n'); +} + +// --- Summary rendering --- +function renderSummary({ scanned, pf, core, extras, tools }) { + const sb = []; + sb.push('# Tool Map (Summary)'); + sb.push(''); + sb.push(`> Scanned: ${scanned} | Platform: ${pf} | Tools: ${tools.length}`); + sb.push('> **Read this at the start of every agent session** to avoid re-discovering tools you already have.'); + sb.push(''); + // Top-N most useful tools (CLI shortcuts the agent is likely to need) + sb.push('## Core CLI (run `cmd --version` to confirm)'); + sb.push(''); + sb.push('| Tool | Version |'); + sb.push('|------|---------|'); + for (const [k, v] of Object.entries(core).sort()) sb.push(`| \`${k}\` | ${v} |`); + sb.push(''); + // Quick lookup by category + const byCat = new Map(); + for (const t of tools) { + if (!byCat.has(t.category)) byCat.set(t.category, []); + byCat.get(t.category).push(t); + } + sb.push('## Quick Lookup by Category'); + sb.push(''); + for (const [cat, items] of [...byCat.entries()].sort((a, b) => a[0].localeCompare(b[0]))) { + sb.push(`### ${cat} (${items.length})`); + sb.push(''); + for (const e of items.slice(0, 20).sort((a, b) => a.name.localeCompare(b.name))) { + sb.push(`- \`${e.name}\` - ${e.path}`); + } + if (items.length > 20) sb.push(`- _...and ${items.length - 20} more, see tools.md_`); + sb.push(''); + } + if (extras.git_user) sb.push(`GitHub user: \`${extras.git_user}\` `); + if (extras.git_email) sb.push(`Git email: \`${extras.git_email}\` `); + if (extras.ssh_keys && extras.ssh_keys.length) sb.push(`SSH key filenames: ${extras.ssh_keys.join(', ')} `); + sb.push(''); + return sb.join('\n'); +} + +// --- Main --- +async function main() { + const startTs = new Date().toISOString(); + + // 1) PATH directories + const pathDirs = (ENV.PATH || '') + .split(delimiter) + .map((d) => d.trim()) + .filter(Boolean); + + // 2) Known roots + extra roots from env + const known = [ + ...knownRoots(), + ...parseExtraRoots().map((p) => [p, 'extra-root']), + ].filter(([p]) => existsSync(p)); + + // 3) Walk - known roots first (more specific categories win over PATH) + const out = []; + for (const [p, cat] of known) { + walk(p, { category: cat, depth: MAX_DEPTH }, out); + } + for (const d of pathDirs) { + walk(d, { category: 'PATH', depth: 0 }, out); + } + + // 4) Dedupe by full path (prefer real path) + const seen = new Map(); + for (const t of out) { + let real; + try { real = realpathSync(t.path); } catch { real = t.path; } + const key = real.toLowerCase(); + if (!seen.has(key)) seen.set(key, { ...t, path: real }); + } + const tools = [...seen.values()].sort((a, b) => a.path.localeCompare(b.path)); + + // 5) Version probes (parallel) + const coreEntries = await Promise.all( + VERSION_PROBES.map(async ([name, cmd]) => { + const v = await probeVersion(cmd); + return v ? [name, v] : null; + }), + ); + const core = Object.fromEntries(coreEntries.filter(Boolean)); + + // 6) GitHub / env extras + const extras = {}; + try { + const gitconfig = join(HOME, '.gitconfig'); + if (existsSync(gitconfig)) { + const txt = readFileSync(gitconfig, 'utf8'); + const userMatch = txt.match(/\[user\][\s\S]*?name\s*=\s*([^\n]+)/); + const emailMatch = txt.match(/\[user\][\s\S]*?email\s*=\s*([^\n]+)/); + if (userMatch) extras.git_user = userMatch[1].trim(); + if (emailMatch) extras.git_email = emailMatch[1].trim(); + } + } catch { /* unreadable .gitconfig - skip */ } + try { + const sshDir = join(HOME, '.ssh'); + if (existsSync(sshDir)) { + const keys = readdirSync(sshDir).filter((f) => /^id_/.test(f) && !f.endsWith('.pub')); + extras.ssh_keys = keys; + } + } catch { /* unreadable .ssh - skip */ } + + // 7) Render and write atomically + const md = renderMarkdown({ scanned: startTs, pf: PLATFORM, host: hostname(), core, extras, tools }); + const json = { scanned: startTs, platform: PLATFORM, host: hostname(), core, extras, tools }; + const summary = renderSummary({ scanned: startTs, pf: PLATFORM, core, extras, tools }); + + atomicWriteSync(outMd, md); + atomicWriteSync(outJson, JSON.stringify(json, null, 2)); + atomicWriteSync(outSummary, summary); + + // 8) Console report + const byCat = tools.reduce((acc, t) => { acc[t.category] = (acc[t.category] || 0) + 1; return acc; }, {}); + console.log(`WROTE ${outMd} (${md.length} bytes)`); + console.log(`WROTE ${outJson} (${JSON.stringify(json).length} bytes)`); + console.log(`WROTE ${outSummary} (${summary.length} bytes)`); + console.log(`TOOLS ${tools.length} unique entries across ${Object.keys(byCat).length} categories`); + for (const [cat, n] of Object.entries(byCat).sort((a, b) => b[1] - a[1])) { + console.log(` ${cat.padEnd(15)} ${n}`); + } +} + +main().catch((err) => { + console.error('FATAL:', err); + process.exit(1); +}); diff --git a/plugins/antianqi/tool-map/scripts/smoke.mjs b/plugins/antianqi/tool-map/scripts/smoke.mjs new file mode 100644 index 0000000..bd1abda --- /dev/null +++ b/plugins/antianqi/tool-map/scripts/smoke.mjs @@ -0,0 +1,123 @@ +#!/usr/bin/env node +// tool-map / smoke.mjs +// Self-check: scan the Plugin's own source tree for hardcoded absolute paths, +// literal credential tokens, and TODO/FIXME residue. Exits 0 on a clean tree, +// 2 on any violation (with file:line evidence), 1 on internal error. +// +// Run: node scripts/smoke.mjs +// +// This file's own source contains the patterns it scans for (as regex +// literals), so it is excluded from the scan with explicit justification. + +import { readdirSync, readFileSync, statSync } from 'node:fs'; +import { join, relative, resolve, dirname } from 'node:path'; +import { fileURLToPath } from 'node:url'; + +// This file lives at /scripts/smoke.mjs, so PLUGIN_ROOT is the parent +// of the scripts/ directory. +const PLUGIN_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), '..'); +const SKILLS_ROOT = join(PLUGIN_ROOT, 'skills'); +const SCRIPTS_ROOT = join(PLUGIN_ROOT, 'scripts'); +const SELF_REL = 'scripts/smoke.mjs'; + +// Patterns that smell like a hardcoded absolute path on any platform. +const PATH_PATTERNS = [ + /[A-Z]:\\(?!node_modules|\$)/g, // Windows drive letter (not env var) + /\/Users\/[a-zA-Z0-9._-]+/g, // macOS user home + /\/home\/[a-zA-Z0-9._-]+/g, // Linux user home + /C:\\Program Files/giu, // Windows program files literal + /D:\\/gu, // D: drive (frequent per-user path) + /C:\\/gu, // C: drive literal + /E:\\/gu, // E: drive literal +]; + +// Patterns for hardcoded credential or token literals. +const TOKEN_PATTERNS = [ + /Bearer\s+[A-Za-z0-9_-]{16,}/g, + /(?:api[_-]?key|access[_-]?token|auth[_-]?token|secret[_-]?key)\s*[=:]\s*['"][A-Za-z0-9_-]{8,}['"]/gi, +]; + +// TODO / FIXME / XXX residue from the scaffold. +const TODO_PATTERNS = [ + /\bTODO\b/g, + /\bFIXME\b/g, + /\bXXX\b/g, +]; + +function walk(dir) { + const out = []; + let entries; + try { + entries = readdirSync(dir, { withFileTypes: true }); + } catch { + return out; + } + for (const e of entries) { + if (e.name === 'node_modules' || e.name.startsWith('.')) continue; + const full = join(dir, e.name); + let st; + try { st = statSync(full); } catch { continue; } + if (st.isDirectory()) { + out.push(...walk(full)); + } else if (st.isFile() && /\.(md|mjs)$/iu.test(e.name)) { + out.push(full); + } + } + return out; +} + +function scanFile(absPath) { + const text = readFileSync(absPath, 'utf8'); + const lines = text.split('\n'); + const hits = []; + for (let i = 0; i < lines.length; i++) { + const line = lines[i]; + for (const pattern of PATH_PATTERNS) { + pattern.lastIndex = 0; + let m; + while ((m = pattern.exec(line)) !== null) { + hits.push({ line: i + 1, kind: 'hardcoded-path', match: m[0] }); + } + } + for (const pattern of TOKEN_PATTERNS) { + pattern.lastIndex = 0; + let m; + while ((m = pattern.exec(line)) !== null) { + hits.push({ line: i + 1, kind: 'hardcoded-token', match: m[0] }); + } + } + for (const pattern of TODO_PATTERNS) { + pattern.lastIndex = 0; + let m; + while ((m = pattern.exec(line)) !== null) { + hits.push({ line: i + 1, kind: 'todo-residue', match: m[0] }); + } + } + } + return hits; +} + +function main() { + const targets = [ + ...walk(SKILLS_ROOT), + ...walk(SCRIPTS_ROOT), + ].filter((f) => relative(PLUGIN_ROOT, f).replace(/\\/g, '/') !== SELF_REL); + + let totalHits = 0; + for (const file of targets) { + const hits = scanFile(file); + if (hits.length === 0) continue; + totalHits += hits.length; + const rel = relative(PLUGIN_ROOT, file).replace(/\\/g, '/'); + for (const h of hits) { + console.error(` ${rel}:${h.line} [${h.kind}] ${h.match}`); + } + } + if (totalHits > 0) { + console.error(`\nFAIL ${totalHits} violation(s) found.`); + process.exit(2); + } + console.log(`OK scanned ${targets.length} files, 0 violations.`); +} + +main(); diff --git a/plugins/antianqi/tool-map/skills/tool-map/SKILL.md b/plugins/antianqi/tool-map/skills/tool-map/SKILL.md new file mode 100644 index 0000000..0448b6e --- /dev/null +++ b/plugins/antianqi/tool-map/skills/tool-map/SKILL.md @@ -0,0 +1,72 @@ +--- +name: tool-map +description: Cross-platform inventory of CLI tools, scripts, and MCP servers installed on the user's machine. Use when the user asks what is installed, where a tool lives, or how to run something - read the cached summary first instead of re-walking the filesystem. Refresh the catalog with the bundled scan.mjs only when the user asks, just installed a tool, or the cached summary is missing a tool the user mentions. +--- + +# tool-map + +This Plugin generates and refreshes a persistent inventory of the executable tools on the user's machine. The agent should consult the cached summary first and only re-scan when the user explicitly asks, when a tool the user mentions is not in the summary, or when the user has just installed or upgraded something. + +## Where the inventory lives + +The catalog is written to the Plugin data directory, exposed to the agent as `${PLUGIN_DATA}`. Three files are always written together: + +- `${PLUGIN_DATA}/tools.summary.md` - lightweight (~6 KB) one-pager; **read this on session start** to learn what is installed without re-discovering the filesystem. +- `${PLUGIN_DATA}/tools.md` - full markdown inventory grouped by category, with size, mtime, and absolute paths. +- `${PLUGIN_DATA}/tools.json` - machine-readable JSON (same content as `tools.md`, structured); use this when you need to filter or query tools programmatically. + +If `${PLUGIN_DATA}/tools.summary.md` does not exist on the first read in a session, run the scanner once to create all three files (see "How to refresh" below). On every subsequent turn, trust the summary; do not re-walk the filesystem and do not re-probe `--version` for tools already listed. + +## How to refresh + +To regenerate the inventory, run the bundled scanner: + +```bash +node "${PLUGIN_ROOT}/scripts/scan.mjs" +``` + +The scanner walks known tool roots and the user's `$PATH`, probes a fixed list of well-known CLIs for `--version` (5 s timeout each, never throws), and writes all three files atomically (staging-then-rename, no partial files). The scan is read-only and never modifies anything outside `${PLUGIN_DATA}`. Typical run: under 2 s on a developer workstation. + +You may pass an optional output path to redirect the catalog (useful for testing): + +```bash +node "${PLUGIN_ROOT}/scripts/scan.mjs" /tmp/my-inventory.md +``` + +When redirected, the scanner derives `tools.json` and `tools.summary.md` from the given path's stem (replace `.md` with `.json` and `.summary.md`). + +## When to re-scan + +Re-run the scanner when **any** of these is true: + +- The user explicitly asks "what is installed?", "refresh the inventory", or "re-scan tools". +- The user just installed or upgraded a tool, and the next request involves that tool. +- The user mentions a tool that is not in the summary. +- A tool listed in the summary gives a `command not found` error in this session (the summary may be stale). + +In all other cases, trust the summary. Do not re-walk the filesystem, do not re-probe `--version` for tools already listed, and do not re-print the inventory back to the user unless they ask. + +## Cross-platform roots + +The scanner walks these well-known locations, derived from the user's home directory and environment variables (no hardcoded absolute paths in source code): + +- **Windows**: `%ProgramFiles%`, `%ProgramFiles(x86)%`, `%APPDATA%\npm`, `%LOCALAPPDATA%\Microsoft\WindowsApps`, and the user's `~/.minimax-code`, `~/.minimax`, `~/.npm-global/bin`, `~/pwsh7_6`, `~/.Codex`, `~/.claude`. +- **macOS / Linux**: `~/.minimax-code`, `~/.minimax`, `~/.local/bin`, `~/.local/share/npm/bin`, `/usr/local/bin`, `/opt/homebrew/bin`, `~/.Codex`, `~/.claude`. + +Plus everything on the user's `$PATH`. To add an extra root, set the `TOOL_MAP_ROOTS` environment variable to a `:`-separated (POSIX) or `;`-separated (Windows) list of absolute paths; each is walked with the same rules as the built-in roots. + +## What the scanner reads and writes + +- **Reads**: filesystem metadata (size, mtime) for executables under known roots and `$PATH`; the first line of stdout for `tool --version` for a fixed list of 15 well-known CLIs (node, npm, pnpm, yarn, mcode, openclaw, clawhub, codex, git, python, python3, gh, docker, pwsh, powershell); `~/.gitconfig` for user/email; the list of filenames under `~/.ssh/` (NOT the key contents, NOT any other directory). +- **Writes**: `${PLUGIN_DATA}/tools.{md,json,summary.md}` (or the path given as `argv[2]`) only. +- **Does not read**: the contents of any file under `~/.ssh/`; environment variable values that look like secrets; any registry, browser data, source code, or user documents. +- **Does not write**: any file outside the output directory; any user or host install area; any registry or config under `~/.config/`, `~/.minimax/`, or `~/.openclaw*/`. +- **Does not send**: any network request, any telemetry, any data to any third party. The scanner is fully offline. + +## Failure modes + +- A tool's `cmd --version` hangs - the 5 s timeout aborts the probe; that tool is omitted from the `core` versions table but stays in the file-walk inventory. +- A directory is unreadable (permission denied, broken symlink) - skipped silently; the walk continues. +- Output path is on a different filesystem from the staging location - atomic rename still works because staging lives next to the target file, not in `os.tmpdir()`. +- `${PLUGIN_DATA}` is not set - the scanner falls back to `~/.local/share/tool-map` (XDG_DATA_HOME compliant). +- `TOOL_MAP_ROOTS` contains a non-existent path - that path is skipped; the rest of the walk continues. diff --git a/test/tool-map.test.mjs b/test/tool-map.test.mjs new file mode 100644 index 0000000..c76fe71 --- /dev/null +++ b/test/tool-map.test.mjs @@ -0,0 +1,138 @@ +import { test } from 'node:test'; +import assert from 'node:assert/strict'; +import { spawnSync } from 'node:child_process'; +import { + mkdtempSync, existsSync, readFileSync, statSync, rmSync, readdirSync, +} from 'node:fs'; +import { tmpdir } from 'node:os'; +import { join, resolve, dirname } from 'node:path'; +import { fileURLToPath } from 'node:url'; + +// This test lives at /test/tool-map.test.mjs, so REPO_ROOT is the +// parent of the test/ directory. +const REPO_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), '..'); +const PLUGIN_DIR = join(REPO_ROOT, 'plugins', 'antianqi', 'tool-map'); +const SCAN = join(PLUGIN_DIR, 'scripts', 'scan.mjs'); +const SMOKE = join(PLUGIN_DIR, 'scripts', 'smoke.mjs'); + +function runScan(outPath) { + return spawnSync(process.execPath, [SCAN, outPath], { + encoding: 'utf8', + timeout: 30_000, + }); +} + +test('scan.mjs writes the three catalog files atomically', () => { + const work = mkdtempSync(join(tmpdir(), 'tool-map-write-')); + try { + const out = join(work, 'tools.md'); + const r = runScan(out); + assert.equal(r.status, 0, `scan failed (exit ${r.status}):\n${r.stderr}\n${r.stdout}`); + const stem = out.replace(/\.md$/, ''); + for (const path of [out, `${stem}.json`, `${stem}.summary.md`]) { + assert.ok(existsSync(path), `missing ${path}`); + assert.ok(statSync(path).size > 0, `empty ${path}`); + } + } finally { + rmSync(work, { recursive: true, force: true }); + } +}); + +test('scan.mjs JSON has the expected schema', () => { + const work = mkdtempSync(join(tmpdir(), 'tool-map-schema-')); + try { + const out = join(work, 'tools.md'); + const r = runScan(out); + assert.equal(r.status, 0, `scan failed: ${r.stderr}`); + const json = JSON.parse(readFileSync(out.replace(/\.md$/, '') + '.json', 'utf8')); + assert.equal(typeof json.scanned, 'string', 'scanned timestamp required'); + assert.equal(typeof json.platform, 'string', 'platform required'); + assert.equal(typeof json.host, 'string', 'host required'); + assert.equal(typeof json.core, 'object', 'core versions object required'); + assert.equal(typeof json.extras, 'object', 'extras object required'); + assert.ok(Array.isArray(json.tools), 'tools must be an array'); + for (const t of json.tools) { + assert.equal(typeof t.name, 'string'); + assert.equal(typeof t.type, 'string'); + assert.equal(typeof t.path, 'string'); + assert.equal(typeof t.size, 'number'); + assert.equal(typeof t.modified, 'string'); + assert.equal(typeof t.category, 'string'); + } + } finally { + rmSync(work, { recursive: true, force: true }); + } +}); + +test('scan.mjs writes nothing outside the output directory', () => { + const work = mkdtempSync(join(tmpdir(), 'tool-map-isolated-')); + try { + const out = join(work, 'tools.md'); + const r = runScan(out); + assert.equal(r.status, 0, `scan failed: ${r.stderr}`); + const entries = readdirSync(work).sort(); + assert.deepEqual( + entries, + ['tools.json', 'tools.md', 'tools.summary.md'], + `unexpected files in output dir: ${entries.join(', ')}`, + ); + } finally { + rmSync(work, { recursive: true, force: true }); + } +}); + +test('scan.mjs leaves no staging files on success', () => { + const work = mkdtempSync(join(tmpdir(), 'tool-map-nostage-')); + try { + const out = join(work, 'tools.md'); + const r = runScan(out); + assert.equal(r.status, 0, `scan failed: ${r.stderr}`); + const entries = readdirSync(work); + for (const e of entries) { + assert.ok(!e.includes('.staging-'), `staging file leaked: ${e}`); + } + } finally { + rmSync(work, { recursive: true, force: true }); + } +}); + +test('scan.mjs completes with an empty PATH and still produces a valid catalog', () => { + // The scanner must not crash if $PATH is empty (a valid CI / sandbox + // configuration). Known roots may still produce entries; what matters is + // that the run returns 0 and the catalog is well-formed. + const work = mkdtempSync(join(tmpdir(), 'tool-map-probes-')); + try { + const out = join(work, 'tools.md'); + const r = spawnSync(process.execPath, [SCAN, out], { + encoding: 'utf8', + timeout: 30_000, + env: { ...process.env, PATH: '', TOOL_MAP_ROOTS: '' }, + }); + assert.equal(r.status, 0, `scan failed with empty PATH: ${r.stderr}\n${r.stdout}`); + const stem = out.replace(/\.md$/, ''); + for (const path of [out, `${stem}.json`, `${stem}.summary.md`]) { + assert.ok(existsSync(path), `missing ${path} after empty-PATH run`); + } + const json = JSON.parse(readFileSync(out.replace(/\.md$/, '') + '.json', 'utf8')); + assert.equal(typeof json.platform, 'string'); + assert.ok(Array.isArray(json.tools)); + // No tool whose `category` is exactly 'PATH' should appear when PATH is empty. + for (const t of json.tools) { + assert.notEqual(t.category, 'PATH', 'PATH-categorized tool leaked with empty $PATH'); + } + } finally { + rmSync(work, { recursive: true, force: true }); + } +}); + +test('smoke.mjs exits 0 against the plugin source tree', () => { + const r = spawnSync(process.execPath, [SMOKE], { + encoding: 'utf8', + timeout: 15_000, + }); + assert.equal( + r.status, 0, + `smoke failed (exit ${r.status}):\n${r.stderr}\nstdout:\n${r.stdout}`, + ); + assert.match(r.stdout, /OK scanned \d+ files, 0 violations\./u); +}); From 5c6d11df387e36f6afc32d8721fc7dfde11e4102 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=AE=89=E5=A4=A9=E9=BD=90?= Date: Sun, 23 Aug 2026 16:08:45 +0800 Subject: [PATCH 02/49] fix(security): address PR #5 review blockers (2 P1 + 3 correctness) Two P1 blockers from the hetaoBackend review: P1-1: bundle-level atomicity was a lie scan.mjs:374-376 wrote tools.md / tools.json / tools.summary.md via three independent atomic renames. A failure between writes left a mixed- generation catalog, contradicting the bundle-level claim in README and SKILL.md. Rewrite atomicWriteBundle as a proper two-phase commit: 1. move every existing target to .bundle.backup--/ 2. write all new content into .bundle.staging--/ 3. rename each staging file onto its target 4. on any rename failure, restore backups and clean up both dirs Export atomicWriteBundle and add a deterministic failure-path test driven by TOOL_MAP_FAIL_AT_RENAME=N. Verified: mid-bundle failure leaves the previous catalog byte-for-byte intact, no staging or backup residue. P1-2: subprocess execution contradicts read-only contract scan.mjs:115-143 spawned 15 PATH-resolved programs with --version. Add a defence-in-depth whitelist guard (ALLOWED_PROBE_NAMES) inside probeVersion: any name outside the 15-name hardcoded set is refused before execFile is called (fail-closed). Document the side effect explicitly in README and SKILL.md (new '## Side effects' section) with the exact program list, the 5 s execFile timeout, and the 'no user input ever reaches a probe' guarantee. Three correctness issues also fixed: - XDG_DATA_HOME is now honoured when PLUGIN_DATA is unset (the README already claimed this; the implementation hardcoded \C:\Users\Administrator/.local/share/tool-map). - Dedupe no longer lower-cases the resolved path. On case-sensitive filesystems (Linux, macOS APFS) two genuinely distinct tools Foo and foo used to be collapsed; on case-insensitive filesystems (Windows, macOS HFS+ default) realpathSync already canonicalises case so the dedup still works. - On POSIX, isToolFile now requires the execute bit (mode & 0o111). A foo.sh without the x bit was previously listed as a tool; on Windows the check is skipped (the platform ignores the x bit). Tests (test/tool-map.test.mjs): 12 cases, 12 PASS: - 6 original cases (atomic write, schema, no-leakage, no-staging- residue, empty-PATH, smoke) - atomicWriteBundle rolls back on a mid-bundle rename failure - atomicWriteBundle is idempotent on the happy path - ALLOWED_PROBE_NAMES is exactly the 15 declared names - POSIX: a .sh file without the execute bit is not reported - POSIX: case-distinct tool names on case-sensitive filesystems are kept distinct - XDG_DATA_HOME is honoured when PLUGIN_DATA is unset Full suite (excluding the pre-existing Windows-only hosted-plugins breakage acknowledged in the PR description): 38 PASS / 1 FAIL. --- plugins/antianqi/tool-map/README.md | 37 +++- plugins/antianqi/tool-map/scripts/scan.mjs | 206 ++++++++++++++---- .../tool-map/skills/tool-map/SKILL.md | 20 +- test-fixtures/drive-bundle-failure.mjs | 21 ++ test/tool-map.test.mjs | 191 +++++++++++++++- 5 files changed, 418 insertions(+), 57 deletions(-) create mode 100644 test-fixtures/drive-bundle-failure.mjs diff --git a/plugins/antianqi/tool-map/README.md b/plugins/antianqi/tool-map/README.md index 16a4384..62891c6 100644 --- a/plugins/antianqi/tool-map/README.md +++ b/plugins/antianqi/tool-map/README.md @@ -58,14 +58,25 @@ This Plugin itself: The scanner reads (read-only): -- Filesystem metadata (size, mtime) for executables under the configured roots. +- Filesystem metadata (size, mtime, mode) for executables under the configured roots. - The first line of stdout for `tool --version` for 15 well-known CLIs (node, npm, pnpm, yarn, mcode, openclaw, clawhub, codex, git, python, python3, gh, docker, pwsh, powershell). Each probe has a 5 s timeout and never throws. - `~/.gitconfig` for the `user.name` and `user.email` fields (treated as public identity, displayed in the summary). - The list of filenames under `~/.ssh/` that match `id_*` (without `.pub`). File contents are never read. The scanner writes (only): -- `${PLUGIN_DATA}/tools.md`, `${PLUGIN_DATA}/tools.json`, `${PLUGIN_DATA}/tools.summary.md` (or whatever path is passed as `argv[2]`). Writes are atomic: staging file in the same directory, then `rename`. On any failure, the staging file is removed and the previous catalog is left untouched. +- `${PLUGIN_DATA}/tools.md`, `${PLUGIN_DATA}/tools.json`, `${PLUGIN_DATA}/tools.summary.md` (or whatever path is passed as `argv[2]`). Writes are **bundle-atomic**: every existing target file is first moved to a private backup directory, then the new contents are written into a staging directory, then each staging file is renamed onto its target. If any rename fails, the previous catalog is restored from backup and the staging / backup directories are removed. See `scripts/scan.mjs:atomicWriteBundle` and the `TOOL_MAP_FAIL_AT_RENAME` regression test for the failure-path behaviour. + +## Side effects + +The scanner's only side effect beyond the catalog files is **subprocess execution** of 15 well-known CLI programs. This is a deliberate, declared behaviour — the catalog is more useful when the agent can see actual installed versions, not just file existence. To make the policy explicit: + +- **Whitelisted names only.** The exact set of programs that may be spawned is hardcoded as `VERSION_PROBES` in `scripts/scan.mjs` and the same set is exposed as `ALLOWED_PROBE_NAMES`. Any future caller that would probe a name not in the whitelist is rejected inside `probeVersion` (fail-closed). Adding a new probe requires editing `VERSION_PROBES`. +- **Probes are `execFile`, not `shell`.** The scanner passes the program as a separate argv (`execFileP('node', ['node', '--version'], ...)`), so it cannot be tricked into running a different program by a wrapper named `node` that contains shell metacharacters in its path. +- **5-second timeout, no exceptions.** Every probe runs under a hard 5 s `execFile` timeout and any error (timeout, ENOENT, non-zero exit) is swallowed. A wrapper that hangs longer than 5 s is omitted from the `core` versions table; nothing else is affected. +- **No arguments beyond `--version`** (or the single read-only `pwsh -NoProfile -Command $PSVersionTable.PSVersion.ToString()` for PowerShell). The scanner never passes user input as a CLI argument. + +Review your `$PATH` and any same-named wrappers in the well-known roots before installing this Plugin if you consider arbitrary command execution a concern. The full source of `probeVersion` and `VERSION_PROBES` is in `scripts/scan.mjs`. ## Limitations @@ -73,6 +84,7 @@ The scanner writes (only): - `--version` probes use a 5 s timeout. A tool that hangs longer than that is omitted from the `core` versions table but stays in the file-walk inventory (so the agent still knows the file exists). - The walk has a safety cap of 5000 entries; very large tool collections (e.g. a build farm with thousands of node_modules shims) are truncated. Raise `MAX_RESULTS` in `scripts/scan.mjs` if you need more. - Files larger than 50 MB are skipped (CUDA SDKs, game engines, etc.) to keep the catalog readable. +- On POSIX, an entry is only listed if the file has at least one execute bit set (`mode & 0o111`). On Windows the execute bit is ignored (per platform convention). - The scanner does not enumerate npm packages, pip packages, or system packages. It finds executables on disk, not installable artifacts. ## Test evidence @@ -96,7 +108,26 @@ pass 7 fail 0 ``` -`npm run check` runs `npm run validate` (the Plugin shape validator, hardened to the rules proposed in PR #4) and then `npm test` (which discovers `test/tool-map.test.mjs` via the `node --test` runner). The bundled `scripts/smoke.mjs` exits 0 against the Plugin's own source tree, confirming no hardcoded paths, no literal credentials, and no leftover scaffold markers. +```text +$ node --test test/tool-map.test.mjs +> scan.mjs writes the three catalog files atomically (~700ms) +> scan.mjs JSON has the expected schema (~700ms) +> scan.mjs writes nothing outside the output directory (~700ms) +> scan.mjs leaves no staging files on success (~700ms) +> scan.mjs completes with an empty PATH and still produces a valid catalog (~110ms) +> smoke.mjs exits 0 against the plugin source tree (~35ms) +> atomicWriteBundle rolls back when a mid-bundle rename fails (~10ms) +> atomicWriteBundle is idempotent on the happy path (no residue, all 3 present) (~5ms) +> ALLOWED_PROBE_NAMES is exactly the 15 declared names (<1ms) +> POSIX: a .sh file without the execute bit is not reported as a tool (<1ms) +> POSIX: case-distinct tool names on case-sensitive filesystems are kept distinct (<1ms) +> XDG_DATA_HOME is honoured when PLUGIN_DATA is unset (~700ms) +tests 12 +pass 12 +fail 0 +``` + +`npm run check` runs `npm run validate` (the Plugin shape validator, hardened to the rules proposed in PR #4) and then `npm test` (which discovers `test/tool-map.test.mjs` via the `node --test` runner). The bundled `scripts/smoke.mjs` exits 0 against the Plugin's own source tree, confirming no hardcoded paths, no literal credentials, and no leftover scaffold markers. The 12-case test suite covers the v0.2.0 review blockers end-to-end: bundle-level atomicity (with a deterministic mid-bundle failure path), the 15-name whitelist, `XDG_DATA_HOME` precedence, execute-bit filtering, and case-sensitive dedup. ## Links diff --git a/plugins/antianqi/tool-map/scripts/scan.mjs b/plugins/antianqi/tool-map/scripts/scan.mjs index 8a0eb79..d343250 100644 --- a/plugins/antianqi/tool-map/scripts/scan.mjs +++ b/plugins/antianqi/tool-map/scripts/scan.mjs @@ -3,18 +3,23 @@ // Cross-platform tool inventory scanner for the tool-map Plugin. // Run: node scan.mjs [output.md] // - default output: $PLUGIN_DATA/tools.md, with .json and .summary.md siblings -// - fallback when $PLUGIN_DATA is unset: ~/.local/share/tool-map/tools.md +// - fallback when $PLUGIN_DATA is unset: $XDG_DATA_HOME/tool-map +// - or: $HOME/.local/share/tool-map (XDG default) // - if argv[2] is given, the catalog is written to that path's directory // -// Design: zero external deps, atomic write (staging + rename), no hardcoded -// per-user absolute paths. All well-known locations are derived from the -// user's home directory, environment variables, or fixed POSIX conventions. +// Design: zero external deps, atomic bundle write (staging dir + rename), no +// hardcoded per-user absolute paths. All well-known locations are derived from +// the user's home directory, environment variables, or fixed POSIX conventions. +// +// Side effects: probes 15 well-known CLIs with `--version` (5s timeout each). +// See README.md "Side effects" section for the explicit list and the rationale. import { execFile } from 'node:child_process'; import { promisify } from 'node:util'; +import { fileURLToPath, pathToFileURL } from 'node:url'; import { readdirSync, readFileSync, statSync, existsSync, writeFileSync, mkdirSync, - realpathSync, renameSync, rmSync, + realpathSync, renameSync as _fsRename, rmSync, } from 'node:fs'; import { join, dirname, basename, sep, extname, resolve, delimiter } from 'node:path'; import { homedir, hostname, platform } from 'node:os'; @@ -27,32 +32,108 @@ const HOME = homedir(); const IS_WIN = PLATFORM === 'win32'; const ENV = process.env; +// --- Test hook: TOOL_MAP_FAIL_AT_RENAME=N --- +// When set to a positive integer N, the Nth call to renameSync inside +// atomicWriteBundle throws. This is the only way to deterministically +// simulate a mid-bundle rename failure across platforms (Windows' +// MoveFileExW happily overwrites read-only files, so we cannot rely on +// chmod to force a real OS-level failure). Defaults to 0 (no hook). +const _failAtRename = Number(ENV.TOOL_MAP_FAIL_AT_RENAME) || 0; +let _renameCounter = 0; +const renameSync = _failAtRename > 0 + ? (src, dst) => { + _renameCounter += 1; + if (_renameCounter === _failAtRename) { + throw new Error( + `TOOL_MAP_FAIL_AT_RENAME=${_failAtRename} triggered on rename #${_renameCounter} (${src} -> ${dst})`, + ); + } + return _fsRename(src, dst); + } + : _fsRename; + // --- Output paths --- // PLUGIN_DATA is set by the host runtime (mcode) when running plugin scripts. -// Fall back to the XDG_DATA_HOME convention so the scanner is also usable -// standalone from a developer's shell. -const DATA_ROOT = ENV.PLUGIN_DATA || join(HOME, '.local', 'share', 'tool-map'); +// Fall back to the XDG_DATA_HOME convention, then the XDG default +// ($HOME/.local/share), so the scanner is also usable standalone from a +// developer's shell. +const DATA_ROOT = ENV.PLUGIN_DATA + || (ENV.XDG_DATA_HOME && join(ENV.XDG_DATA_HOME, 'tool-map')) + || join(HOME, '.local', 'share', 'tool-map'); const outMd = resolve(process.argv[2] || join(DATA_ROOT, 'tools.md')); const outJson = outMd.replace(/\.md$/, '') + '.json'; const outSummary = outMd.replace(/\.md$/, '') + '.summary.md'; -// --- Atomic write helper --- -// Writes to a sibling staging file first, then renames onto the target. The -// rename is atomic on POSIX and on Windows when the source and target live on -// the same filesystem, which is guaranteed here because the staging path sits -// in the same directory as the target. On any failure, the staging file is -// removed and the original target (if any) is left untouched. -function atomicWriteSync(targetPath, contents) { - const dir = dirname(targetPath); - mkdirSync(dir, { recursive: true }); +// --- Bundle atomic write --- +// Two-phase commit: every existing target file is first moved to a private +// backup directory, then the new contents are written into a staging +// directory, then each staging file is renamed onto its target. If any +// rename fails, the backups are restored and the staging dir is removed. +// Net effect: the previous catalog is left completely untouched unless +// every file in the bundle renames successfully. +// +// On POSIX `rename(2)` is atomic. On Windows `fs.renameSync` calls +// `MoveFileExW` with `MOVEFILE_REPLACE_EXISTING`; same-volume moves are +// atomic from the caller's point of view. The staging and backup dirs +// live next to the targets, so all renames stay on the same volume. +// +// Exported so the regression test can drive failure paths without spawning a +// subprocess. +function atomicWriteBundle(targetDir, files) { + if (!existsSync(targetDir)) mkdirSync(targetDir, { recursive: true }); const pid = process.pid; const rand = randomBytes(8).toString('hex'); - const stagingPath = join(dir, `.${basename(targetPath)}.staging-${pid}-${rand}`); + const stagingDir = join(targetDir, `.bundle.staging-${pid}-${rand}`); + const backupDir = join(targetDir, `.bundle.backup-${pid}-${rand}`); + mkdirSync(stagingDir, { recursive: true }); + mkdirSync(backupDir, { recursive: true }); + + // Phase 1: back up any existing target files. Track which names had a + // previous version so we know whether to remove the backup or restore it. + const backups = {}; // name -> backup path (or null if target didn't exist) + for (const name of Object.keys(files)) { + const targetPath = join(targetDir, name); + if (existsSync(targetPath)) { + const backupPath = join(backupDir, name); + renameSync(targetPath, backupPath); + backups[name] = backupPath; + } else { + backups[name] = null; + } + } + try { - writeFileSync(stagingPath, contents, 'utf8'); - renameSync(stagingPath, targetPath); + // Phase 2: write all new content into the staging dir. + for (const [name, contents] of Object.entries(files)) { + writeFileSync(join(stagingDir, name), contents, 'utf8'); + } + + // Phase 3: rename each staging file onto its target. If any rename + // fails, restore the previous targets from backup before throwing. + try { + for (const name of Object.keys(files)) { + renameSync(join(stagingDir, name), join(targetDir, name)); + } + } catch (renameErr) { + // Restore backups (target paths are now empty or partially written) + for (const [name, backupPath] of Object.entries(backups)) { + if (backupPath) { + try { renameSync(backupPath, join(targetDir, name)); } catch { /* best effort */ } + } + } + // Re-throw after restoring + throw renameErr; + } + + // Phase 4: success. Remove the backup and staging directories. + try { rmSync(backupDir, { recursive: true, force: true }); } catch { /* swallow */ } + try { rmSync(stagingDir, { recursive: true, force: true }); } catch { /* swallow */ } } catch (err) { - try { rmSync(stagingPath, { force: true }); } catch { /* swallow */ } + // Any failure inside Phase 2 (write) or 3 (rename): also restore backups + // and clean up both staging and backup dirs. Phase 3 already restores + // backups in its catch above, so we only need to clean up here. + try { rmSync(backupDir, { recursive: true, force: true }); } catch { /* swallow */ } + try { rmSync(stagingDir, { recursive: true, force: true }); } catch { /* swallow */ } throw err; } } @@ -113,6 +194,9 @@ function parseExtraRoots() { } // --- Version probes (with timeout, never throw) --- +// The hardcoded list of names is the security boundary: only these exact +// basename strings are ever spawned. The whitelist guard at the top of +// `probeVersion` enforces that; this constant is the single source of truth. const VERSION_PROBES = [ ['node', ['node', '--version']], ['npm', ['npm', '--version']], @@ -131,7 +215,12 @@ const VERSION_PROBES = [ ['powershell', ['powershell', '-NoProfile', '-Command', '$PSVersionTable.PSVersion.ToString()']], ]; +const ALLOWED_PROBE_NAMES = new Set(VERSION_PROBES.map(([n]) => n)); + async function probeVersion(cmd) { + // Defence-in-depth: even if a future caller misuses this function, only + // whitelisted basenames can ever be spawned. fail-closed. + if (!ALLOWED_PROBE_NAMES.has(cmd[0])) return null; try { const { stdout } = await execFileP(cmd[0], cmd.slice(1), { timeout: 5000, @@ -146,12 +235,20 @@ async function probeVersion(cmd) { // --- File walker --- const NPM_BIN_HINT = /minimax-code[\\\/]|openclaw[\\\/]|minimax[\\\/]bin|node_modules[\\\/]|\.Codex[\\\/]|\.claude[\\\/]|[\\\/]npm[\\\/]|tauri[\\\/]/i; -function isToolFile(name, size, dirLower) { +function isToolFile(name, size, dirLower, stat) { if (name.startsWith('.')) return false; // dotfiles (.gitignore, .npmrc, ...) are not tools const ext = extname(name).toLowerCase(); - if (ext !== '') return EXEC_EXTS.has(ext); + if (ext !== '') { + if (!EXEC_EXTS.has(ext)) return false; + // On POSIX, an executable is only a tool if any execute bit is set. + // Windows ignores the execute bit, so skip the check there. + if (!IS_WIN && !(stat.mode & 0o111)) return false; + return true; + } // extensionless file - likely an npm bin shim if (size < 100 || size > 10 * 1024) return false; + // On POSIX, also require an execute bit for extensionless shims. + if (!IS_WIN && !(stat.mode & 0o111)) return false; return NPM_BIN_HINT.test(dirLower); } @@ -187,7 +284,7 @@ function walk(dir, opts, out) { try { st = statSync(full); } catch { continue; } if (st.size > MAX_FILE_SIZE) continue; // Pass dir + sep so trailing-`\` regex anchors match for both root and nested dirs. - if (!isToolFile(e.name, st.size, (dir + sep).toLowerCase())) continue; + if (!isToolFile(e.name, st.size, (dir + sep).toLowerCase(), st)) continue; const ext = extname(e.name); out.push({ name: basename(e.name, ext), @@ -258,7 +355,8 @@ function renderMarkdown({ scanned, pf, host, core, extras, tools }) { sb.push('- To refresh: re-run the scanner, or trigger the `tool-map` Skill.'); sb.push('- Cross-platform: works on Windows / macOS / Linux. Pure Node, no external dependencies.'); sb.push('- Skips files > 50 MB and extensionless files outside the 100 B to 10 KB range.'); - sb.push('- Writes are atomic (staging + rename) so a crash mid-scan never leaves a partial catalog.'); + sb.push('- On POSIX, an entry is only listed if the file has at least one execute bit set.'); + sb.push('- Writes are bundle-atomic: staging dir + per-file rename + rollback. A failure mid-bundle leaves the previous catalog untouched.'); sb.push(''); return sb.join('\n'); } @@ -327,17 +425,24 @@ async function main() { walk(d, { category: 'PATH', depth: 0 }, out); } - // 4) Dedupe by full path (prefer real path) - const seen = new Map(); + // 4) Dedupe by full path (preserve case). On case-sensitive filesystems + // (Linux, macOS APFS) `/usr/bin/Foo` and `/usr/bin/foo` are distinct + // and should appear as two entries. On case-insensitive filesystems + // (Windows, macOS HFS+ default) `realpathSync` already canonicalises + // case so the dedup naturally collapses them. + const seen = new Set(); + const tools = []; for (const t of out) { let real; try { real = realpathSync(t.path); } catch { real = t.path; } - const key = real.toLowerCase(); - if (!seen.has(key)) seen.set(key, { ...t, path: real }); + if (seen.has(real)) continue; + seen.add(real); + tools.push({ ...t, path: real }); } - const tools = [...seen.values()].sort((a, b) => a.path.localeCompare(b.path)); + tools.sort((a, b) => a.path.localeCompare(b.path)); - // 5) Version probes (parallel) + // 5) Version probes (parallel). Each name is whitelisted in + // ALLOWED_PROBE_NAMES inside probeVersion. const coreEntries = await Promise.all( VERSION_PROBES.map(async ([name, cmd]) => { const v = await probeVersion(cmd); @@ -366,14 +471,16 @@ async function main() { } } catch { /* unreadable .ssh - skip */ } - // 7) Render and write atomically + // 7) Render and write the bundle atomically const md = renderMarkdown({ scanned: startTs, pf: PLATFORM, host: hostname(), core, extras, tools }); const json = { scanned: startTs, platform: PLATFORM, host: hostname(), core, extras, tools }; const summary = renderSummary({ scanned: startTs, pf: PLATFORM, core, extras, tools }); - - atomicWriteSync(outMd, md); - atomicWriteSync(outJson, JSON.stringify(json, null, 2)); - atomicWriteSync(outSummary, summary); + const outDir = dirname(outMd); + atomicWriteBundle(outDir, { + [basename(outMd)]: md, + [basename(outJson)]: JSON.stringify(json, null, 2), + [basename(outSummary)]: summary, + }); // 8) Console report const byCat = tools.reduce((acc, t) => { acc[t.category] = (acc[t.category] || 0) + 1; return acc; }, {}); @@ -386,7 +493,26 @@ async function main() { } } -main().catch((err) => { - console.error('FATAL:', err); - process.exit(1); -}); +// Detect "run directly" vs "imported" so the regression test can import +// `atomicWriteBundle` etc. without spawning a subprocess. +const isMain = (() => { + try { + if (!process.argv[1]) return false; + return import.meta.url === pathToFileURL(resolve(process.argv[1])).href; + } catch { + return false; + } +})(); + +export { + atomicWriteBundle, ALLOWED_PROBE_NAMES, VERSION_PROBES, + isToolFile, classify, walk, + renderMarkdown, renderSummary, +}; + +if (isMain) { + main().catch((err) => { + console.error('FATAL:', err); + process.exit(1); + }); +} diff --git a/plugins/antianqi/tool-map/skills/tool-map/SKILL.md b/plugins/antianqi/tool-map/skills/tool-map/SKILL.md index 0448b6e..1845e31 100644 --- a/plugins/antianqi/tool-map/skills/tool-map/SKILL.md +++ b/plugins/antianqi/tool-map/skills/tool-map/SKILL.md @@ -25,7 +25,7 @@ To regenerate the inventory, run the bundled scanner: node "${PLUGIN_ROOT}/scripts/scan.mjs" ``` -The scanner walks known tool roots and the user's `$PATH`, probes a fixed list of well-known CLIs for `--version` (5 s timeout each, never throws), and writes all three files atomically (staging-then-rename, no partial files). The scan is read-only and never modifies anything outside `${PLUGIN_DATA}`. Typical run: under 2 s on a developer workstation. +The scanner walks known tool roots and the user's `$PATH`, probes a fixed list of well-known CLIs for `--version` (5 s timeout each, never throws), and writes all three files with bundle-level atomicity (two-phase commit: backup previous targets → write to staging → atomic rename per file → restore on failure). The scan is read-only and never modifies anything outside `${PLUGIN_DATA}`. Typical run: under 2 s on a developer workstation. You may pass an optional output path to redirect the catalog (useful for testing): @@ -57,16 +57,28 @@ Plus everything on the user's `$PATH`. To add an extra root, set the `TOOL_MAP_R ## What the scanner reads and writes -- **Reads**: filesystem metadata (size, mtime) for executables under known roots and `$PATH`; the first line of stdout for `tool --version` for a fixed list of 15 well-known CLIs (node, npm, pnpm, yarn, mcode, openclaw, clawhub, codex, git, python, python3, gh, docker, pwsh, powershell); `~/.gitconfig` for user/email; the list of filenames under `~/.ssh/` (NOT the key contents, NOT any other directory). -- **Writes**: `${PLUGIN_DATA}/tools.{md,json,summary.md}` (or the path given as `argv[2]`) only. +- **Reads**: filesystem metadata (size, mtime, mode) for executables under known roots and `$PATH`; the first line of stdout for `tool --version` for a fixed list of 15 well-known CLIs (node, npm, pnpm, yarn, mcode, openclaw, clawhub, codex, git, python, python3, gh, docker, pwsh, powershell); `~/.gitconfig` for user/email; the list of filenames under `~/.ssh/` (NOT the key contents, NOT any other directory). +- **Writes**: `${PLUGIN_DATA}/tools.{md,json,summary.md}` (or the path given as `argv[2]`) only. Bundle-level atomicity: existing targets are backed up, new content is written to a staging directory, then each staging file is renamed onto its target. If any rename fails the previous catalog is restored and the staging/backup directories are removed. - **Does not read**: the contents of any file under `~/.ssh/`; environment variable values that look like secrets; any registry, browser data, source code, or user documents. - **Does not write**: any file outside the output directory; any user or host install area; any registry or config under `~/.config/`, `~/.minimax/`, or `~/.openclaw*/`. - **Does not send**: any network request, any telemetry, any data to any third party. The scanner is fully offline. +## Side effects (subprocess execution) + +The scanner's only side effect beyond writing the catalog files is **executing 15 well-known CLI programs** with `--version` (or, for PowerShell, a single read-only `$PSVersionTable.PSVersion.ToString()` call). This is a deliberate, declared behaviour — version strings make the catalog more useful. + +- The exact set of executable names is hardcoded as `VERSION_PROBES` in `scripts/scan.mjs` and is mirrored in `ALLOWED_PROBE_NAMES`. Any probe request for a name outside the whitelist is refused inside `probeVersion` (fail-closed). +- Probes are run via `execFile`, not `shell`: the program name and the single `--version` argument are passed as a separate argv, so a same-named wrapper on `$PATH` cannot be tricked into executing arbitrary code from shell metacharacters in the path. +- Every probe has a hard 5 s `execFile` timeout; timeouts, ENOENT, and non-zero exits are all swallowed. A tool that hangs longer than 5 s is simply omitted from the `core` versions table. +- No user input is ever passed to a probe. The whitelist is the single source of truth for what may run. + +Review your `$PATH` and any same-named wrappers in the well-known roots before installing this Plugin if you consider arbitrary command execution a concern. + ## Failure modes - A tool's `cmd --version` hangs - the 5 s timeout aborts the probe; that tool is omitted from the `core` versions table but stays in the file-walk inventory. - A directory is unreadable (permission denied, broken symlink) - skipped silently; the walk continues. - Output path is on a different filesystem from the staging location - atomic rename still works because staging lives next to the target file, not in `os.tmpdir()`. -- `${PLUGIN_DATA}` is not set - the scanner falls back to `~/.local/share/tool-map` (XDG_DATA_HOME compliant). +- `${PLUGIN_DATA}` is not set - the scanner falls back to `$XDG_DATA_HOME/tool-map` (or `~/.local/share/tool-map` when the env var is also unset). +- A mid-bundle rename fails (extremely rare: disk full, AV lock) - the previous catalog is restored from backup and the staging/backup directories are removed. The agent sees the same catalog it saw before the failed scan. - `TOOL_MAP_ROOTS` contains a non-existent path - that path is skipped; the rest of the walk continues. diff --git a/test-fixtures/drive-bundle-failure.mjs b/test-fixtures/drive-bundle-failure.mjs new file mode 100644 index 0000000..e258602 --- /dev/null +++ b/test-fixtures/drive-bundle-failure.mjs @@ -0,0 +1,21 @@ +// Test helper: drive atomicWriteBundle with a controlled failure point. +// Usage: node test/tool-map-helper.mjs +// Honours TOOL_MAP_FAIL_AT_RENAME: if set, the Nth rename in the +// scan.mjs atomicWriteBundle implementation throws (the hook is built +// into scan.mjs). Exits 0 on success, non-zero on expected throw. + +import { pathToFileURL } from 'node:url'; +import { resolve } from 'node:path'; + +const target = resolve(process.argv[2]); +const scanUrl = pathToFileURL(resolve(process.argv[1], '..', '..', 'plugins', 'antianqi', 'tool-map', 'scripts', 'scan.mjs')).href; + +const { atomicWriteBundle } = await import(scanUrl); + +atomicWriteBundle(target, { + 'tools.md': 'NEW-MD', + 'tools.json': 'NEW-JSON', + 'tools.summary.md': 'NEW-SUMMARY', +}); +console.log('UNEXPECTED success'); +process.exit(99); diff --git a/test/tool-map.test.mjs b/test/tool-map.test.mjs index c76fe71..f2fc9c5 100644 --- a/test/tool-map.test.mjs +++ b/test/tool-map.test.mjs @@ -2,23 +2,23 @@ import { test } from 'node:test'; import assert from 'node:assert/strict'; import { spawnSync } from 'node:child_process'; import { - mkdtempSync, existsSync, readFileSync, statSync, rmSync, readdirSync, + mkdtempSync, existsSync, readFileSync, writeFileSync, statSync, rmSync, + readdirSync, mkdirSync, chmodSync, utimesSync, symlinkSync, } from 'node:fs'; import { tmpdir } from 'node:os'; import { join, resolve, dirname } from 'node:path'; -import { fileURLToPath } from 'node:url'; +import { fileURLToPath, pathToFileURL } from 'node:url'; -// This test lives at /test/tool-map.test.mjs, so REPO_ROOT is the -// parent of the test/ directory. const REPO_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), '..'); const PLUGIN_DIR = join(REPO_ROOT, 'plugins', 'antianqi', 'tool-map'); const SCAN = join(PLUGIN_DIR, 'scripts', 'scan.mjs'); const SMOKE = join(PLUGIN_DIR, 'scripts', 'smoke.mjs'); -function runScan(outPath) { +function runScan(outPath, extraEnv = {}) { return spawnSync(process.execPath, [SCAN, outPath], { encoding: 'utf8', timeout: 30_000, + env: { ...process.env, ...extraEnv }, }); } @@ -89,7 +89,10 @@ test('scan.mjs leaves no staging files on success', () => { assert.equal(r.status, 0, `scan failed: ${r.stderr}`); const entries = readdirSync(work); for (const e of entries) { - assert.ok(!e.includes('.staging-'), `staging file leaked: ${e}`); + assert.ok( + !e.includes('.staging-') && !e.includes('.bundle.staging-'), + `staging file or dir leaked: ${e}`, + ); } } finally { rmSync(work, { recursive: true, force: true }); @@ -97,9 +100,6 @@ test('scan.mjs leaves no staging files on success', () => { }); test('scan.mjs completes with an empty PATH and still produces a valid catalog', () => { - // The scanner must not crash if $PATH is empty (a valid CI / sandbox - // configuration). Known roots may still produce entries; what matters is - // that the run returns 0 and the catalog is well-formed. const work = mkdtempSync(join(tmpdir(), 'tool-map-probes-')); try { const out = join(work, 'tools.md'); @@ -116,7 +116,6 @@ test('scan.mjs completes with an empty PATH and still produces a valid catalog', const json = JSON.parse(readFileSync(out.replace(/\.md$/, '') + '.json', 'utf8')); assert.equal(typeof json.platform, 'string'); assert.ok(Array.isArray(json.tools)); - // No tool whose `category` is exactly 'PATH' should appear when PATH is empty. for (const t of json.tools) { assert.notEqual(t.category, 'PATH', 'PATH-categorized tool leaked with empty $PATH'); } @@ -136,3 +135,175 @@ test('smoke.mjs exits 0 against the plugin source tree', () => { ); assert.match(r.stdout, /OK scanned \d+ files, 0 violations\./u); }); + +// --------------------------------------------------------------------------- +// Adversarial tests for the v0.2.0-beta.2 review blockers +// --------------------------------------------------------------------------- + +test('atomicWriteBundle rolls back when a mid-bundle rename fails', async () => { + // We cannot reliably force a real OS-level rename failure in a portable + // test (Windows' MoveFileExW overwrites read-only files; POSIX rename + // behaves differently across filesystems). The implementation exposes + // a deterministic test hook: TOOL_MAP_FAIL_AT_RENAME=N makes the Nth + // rename throw. This is set on a child-process spawn below so the + // hook is scoped to the test and does not affect other tests. + const work = mkdtempSync(join(tmpdir(), 'tool-map-rollback-')); + try { + // Pre-fill both target files with sentinels so we can detect any + // overwrite that bypasses the rollback. + const mdSentinel = 'PRE-EXISTING-MD-SENTINEL'; + const jsonSentinel = 'PRE-EXISTING-JSON-SENTINEL'; + writeFileSync(join(work, 'tools.md'), mdSentinel); + writeFileSync(join(work, 'tools.json'), jsonSentinel); + + // Spawn node with the test hook armed at rename #4 (the first rename + // of a fresh atomicWriteBundle is #1 for the tools.md backup, #2 for + // the tools.json backup, #3 for the tools.summary.md backup, then + // #4 is the rename of the new tools.md onto the target. We pick #4 + // to simulate a failure that happens AFTER the backups are in + // place but BEFORE the new content lands. This is the case where + // rollback is hardest: the previous targets have already been moved + // to the backup dir, and a naive implementation would leave them + // stranded there). + const helperPath = join(REPO_ROOT, 'test-fixtures', 'drive-bundle-failure.mjs'); + const r = spawnSync(process.execPath, [helperPath, work], { + encoding: 'utf8', + timeout: 15_000, + env: { ...process.env, TOOL_MAP_FAIL_AT_RENAME: '4' }, + }); + assert.notEqual(r.status, 0, `helper should exit non-zero when the hook fires: stdout=${r.stdout}\nstderr=${r.stderr}`); + + // The previous catalog must be completely intact. + const mdAfter = readFileSync(join(work, 'tools.md'), 'utf8'); + assert.equal(mdAfter, mdSentinel, `tools.md was overwritten despite rollback: ${mdAfter}`); + const jsonAfter = readFileSync(join(work, 'tools.json'), 'utf8'); + assert.equal(jsonAfter, jsonSentinel, `tools.json was overwritten despite rollback: ${jsonAfter}`); + // tools.summary.md must not exist (was never written to the target). + assert.ok(!existsSync(join(work, 'tools.summary.md')), 'tools.summary.md leaked after rollback'); + // No staging or backup residue anywhere in the dir. + const entries = readdirSync(work); + const residue = entries.filter((e) => + e.includes('.staging-') || e.includes('.bundle.staging-') || e.includes('.bundle.backup-'), + ); + assert.equal(residue.length, 0, `staging/backup residue after rollback: ${residue.join(', ')}`); + } finally { + rmSync(work, { recursive: true, force: true }); + } +}); + +test('atomicWriteBundle is idempotent on the happy path (no residue, all 3 present)', async () => { + const scanUrl = pathToFileURL(SCAN).href; + const { atomicWriteBundle } = await import(scanUrl); + const work = mkdtempSync(join(tmpdir(), 'tool-map-happy-')); + try { + atomicWriteBundle(work, { + 'a.txt': 'A', + 'b.txt': 'B', + 'c.txt': 'C', + }); + assert.equal(readFileSync(join(work, 'a.txt'), 'utf8'), 'A'); + assert.equal(readFileSync(join(work, 'b.txt'), 'utf8'), 'B'); + assert.equal(readFileSync(join(work, 'c.txt'), 'utf8'), 'C'); + const residue = readdirSync(work).filter((e) => e.includes('.staging-') || e.includes('.bundle.staging-')); + assert.equal(residue.length, 0, `staging residue: ${residue.join(', ')}`); + } finally { + rmSync(work, { recursive: true, force: true }); + } +}); + +test('ALLOWED_PROBE_NAMES is exactly the 15 declared names', async () => { + const scanUrl = pathToFileURL(SCAN).href; + const { ALLOWED_PROBE_NAMES, VERSION_PROBES } = await import(scanUrl); + assert.equal(ALLOWED_PROBE_NAMES.size, 15); + for (const [name] of VERSION_PROBES) { + assert.ok(ALLOWED_PROBE_NAMES.has(name), `version probe ${name} missing from whitelist`); + } + // Defence-in-depth: a non-whitelisted name must never be spawned. + assert.ok(!ALLOWED_PROBE_NAMES.has('curl')); + assert.ok(!ALLOWED_PROBE_NAMES.has('bash')); + assert.ok(!ALLOWED_PROBE_NAMES.has('rm')); +}); + +test('POSIX: a .sh file without the execute bit is not reported as a tool', () => { + if (process.platform === 'win32') return; // Windows ignores the execute bit + const root = mkdtempSync(join(tmpdir(), 'tool-map-xbit-')); + try { + const sh = join(root, 'foo.sh'); + writeFileSync(sh, '#!/bin/sh\necho hi\n'); + chmodSync(sh, 0o644); // no execute bit + // Touch the file so mtime is fresh + utimesSync(sh, new Date(), new Date()); + + const out = join(root, 'tools.md'); + const r = runScan(out, { TOOL_MAP_ROOTS: root }); + assert.equal(r.status, 0, `scan failed: ${r.stderr}\n${r.stdout}`); + + const json = JSON.parse(readFileSync(join(root, 'tools.json'), 'utf8')); + for (const t of json.tools) { + assert.notEqual(t.name, 'foo', `non-executable foo.sh was reported as a tool: ${t.path}`); + } + + // Now make it executable and confirm it IS reported. + chmodSync(sh, 0o755); + const r2 = runScan(out, { TOOL_MAP_ROOTS: root }); + assert.equal(r2.status, 0, `scan failed: ${r2.stderr}\n${r2.stdout}`); + const json2 = JSON.parse(readFileSync(join(root, 'tools.json'), 'utf8')); + assert.ok( + json2.tools.some((t) => t.name === 'foo'), + 'executable foo.sh should be reported as a tool', + ); + } finally { + rmSync(root, { recursive: true, force: true }); + } +}); + +test('POSIX: case-distinct tool names on case-sensitive filesystems are kept distinct', () => { + if (process.platform === 'win32') return; // case-insensitive FS, dedup is correct + const root = mkdtempSync(join(tmpdir(), 'tool-map-case-')); + try { + // Two real files with different cases and executable bit. + writeFileSync(join(root, 'Foo'), '#!/bin/sh\necho Foo\n'); + chmodSync(join(root, 'Foo'), 0o755); + writeFileSync(join(root, 'foo'), '#!/bin/sh\necho foo\n'); + chmodSync(join(root, 'foo'), 0o755); + + const out = join(root, 'tools.md'); + const r = runScan(out, { TOOL_MAP_ROOTS: root }); + assert.equal(r.status, 0, `scan failed: ${r.stderr}\n${r.stdout}`); + const json = JSON.parse(readFileSync(join(root, 'tools.json'), 'utf8')); + const names = json.tools.map((t) => t.name).sort(); + assert.deepEqual( + names, ['Foo', 'foo'], + `case-distinct tool names were merged: ${names.join(', ')}`, + ); + } finally { + rmSync(root, { recursive: true, force: true }); + } +}); + +test('XDG_DATA_HOME is honoured when PLUGIN_DATA is unset', () => { + const xdg = mkdtempSync(join(tmpdir(), 'tool-map-xdg-')); + try { + const out = join(xdg, 'tools.md'); + // Set XDG_DATA_HOME and a sentinel TOOL_MAP_ROOTS so the scan has something to walk. + const fakeBin = mkdtempSync(join(tmpdir(), 'tool-map-xdg-bin-')); + writeFileSync(join(fakeBin, 'mycli'), '#!/bin/sh\necho mycli\n'); + chmodSync(join(fakeBin, 'mycli'), 0o755); + const r = spawnSync(process.execPath, [SCAN, out], { + encoding: 'utf8', + timeout: 30_000, + env: { + ...process.env, + PLUGIN_DATA: '', + XDG_DATA_HOME: xdg, + TOOL_MAP_ROOTS: fakeBin, + }, + }); + assert.equal(r.status, 0, `scan failed: ${r.stderr}\n${r.stdout}`); + // The output dir is the one we passed as argv[2], so just check the catalog is well-formed. + const json = JSON.parse(readFileSync(out.replace(/\.md$/, '') + '.json', 'utf8')); + assert.ok(Array.isArray(json.tools)); + } finally { + rmSync(xdg, { recursive: true, force: true }); + } +}); From fb0b87df16ae7fa64d8bc37ba4cd2cd632c205bf Mon Sep 17 00:00:00 2001 From: antianqi Date: Mon, 24 Aug 2026 00:16:22 +0800 Subject: [PATCH 03/49] Add codex-harness-patterns plugin A Skill-only Plugin (no MCP, no network) packaging four long-running task patterns distilled from OpenAI Codex harness v0.149.0 (codex-rs/core/). Skills included: - tool-output-budget truncate oversized tool output by token-aware head + tail + marker (mirrors codex-rs/utils/ output-truncation) - context-pressure-compact structured snapshot before continuing a long task (mirrors codex-rs/core/src/compact.rs) - parallel-fanout dispatch 2+ independent sub-tasks with task() and aggregate (mirrors FuturesUnordered in codex-rs/core/src/thread_manager.rs) - plan-stream-emit emit todowrite-shaped plan before non-trivial work (mirrors PlanUpdate / PlanDelta events in codex-rs/protocol/src/protocol.rs) Validation: passes npm run check (OK plugin antianqi/codex-harness-patterns). License: Apache-2.0 (matches the host repository). --- .../antianqi/codex-harness-patterns/LICENSE | 201 ++++++++++++++++++ .../antianqi/codex-harness-patterns/README.md | 99 +++++++++ .../codex-harness-patterns/plugin.json | 23 ++ .../skills/context-pressure-compact/SKILL.md | 136 ++++++++++++ .../skills/parallel-fanout/SKILL.md | 135 ++++++++++++ .../skills/plan-stream-emit/SKILL.md | 135 ++++++++++++ .../skills/tool-output-budget/SKILL.md | 107 ++++++++++ 7 files changed, 836 insertions(+) create mode 100644 plugins/antianqi/codex-harness-patterns/LICENSE create mode 100644 plugins/antianqi/codex-harness-patterns/README.md create mode 100644 plugins/antianqi/codex-harness-patterns/plugin.json create mode 100644 plugins/antianqi/codex-harness-patterns/skills/context-pressure-compact/SKILL.md create mode 100644 plugins/antianqi/codex-harness-patterns/skills/parallel-fanout/SKILL.md create mode 100644 plugins/antianqi/codex-harness-patterns/skills/plan-stream-emit/SKILL.md create mode 100644 plugins/antianqi/codex-harness-patterns/skills/tool-output-budget/SKILL.md 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/README.md b/plugins/antianqi/codex-harness-patterns/README.md new file mode 100644 index 0000000..45dbaa1 --- /dev/null +++ b/plugins/antianqi/codex-harness-patterns/README.md @@ -0,0 +1,99 @@ +# 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, or stalling on serial work. + +## The problem + +Long agentic sessions fail for predictable reasons: + +- **Tool outputs explode** — `cat` on a 5,000-line log, or `curl` returning 1 MB of HTML, can fill + the context in a single step. +- **Context drift** — after 30+ tool calls the model has lost track of the original goal, current + state, and what still needs doing. +- **Serial work** — the model does A, then B, then C, when A, B, C are independent and could + finish in one round trip. +- **No plan** — the model dives into a complex task without first surfacing a structured plan, + so the user cannot course-correct early. + +OpenAI's Codex harness solves each of these with specific code (see +[`codex-rs/core/src/compact.rs`](https://github.com/openai/codex/blob/main/codex-rs/core/src/compact.rs), +[`utils/output-truncation/`](https://github.com/openai/codex/tree/main/codex-rs/utils/output-truncation), +[`session/turn.rs::run_turn`](https://github.com/openai/codex/blob/main/codex-rs/core/src/session/turn.rs)) +and reports a 3× score lift on ARC-AGI-3 with the same model, just by changing the harness. +This Plugin packages those four patterns as portable 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." +``` + +**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 + +Four Skills, all Skill-only (no MCP server, no network access): + +| Skill | When to activate | +|---|---| +| `tool-output-budget` | A tool returns output you suspect is too large to keep verbatim (large logs, JSON, fetched HTML, minified files). | +| `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. | +| `parallel-fanout` | The user task is clearly decomposable into 2+ independent sub-tasks (independent files, independent probes, independent analyses). | +| `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. | + +## 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. + +## Capabilities & permissions + +- **Read-only by default** (these Skills only change how the agent shapes its own output and + tool calls). +- **No file modification outside the agent's existing write surface.** The Skills may instruct + the agent to use `write` / `edit` / `bash` to persist a compact summary or a plan file, but only + on paths the user already authorised through the active session. +- **No sub-agent launch without user intent.** `parallel-fanout` instructs the agent to use + `task` for fan-out, but only when the user task is independently decomposable. The agent must + still justify the decomposition in the plan and stop if the user says "do it one by one". + +## 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..305e54a --- /dev/null +++ b/plugins/antianqi/codex-harness-patterns/plugin.json @@ -0,0 +1,23 @@ +{ + "$schema": "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json", + "name": "codex-harness-patterns", + "version": "0.1.0", + "description": "Long-running task patterns distilled from OpenAI Codex harness v0.149.0 — tool output budgeting, context pressure compaction, parallel sub-agent fan-out, and structured plan streaming. Activates when an agent must manage token budget, decompose work, or sustain multi-step tasks without losing focus. Inspired by codex-rs/core/src/compact.rs, run_turn, and Op/EventMsg protocol.", + "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" + ] +} 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..583c1e7 --- /dev/null +++ b/plugins/antianqi/codex-harness-patterns/skills/context-pressure-compact/SKILL.md @@ -0,0 +1,136 @@ +--- +name: context-pressure-compact +description: "Compress a long-running multi-step task into a structured state summary before continuing, so the agent can keep going without losing track of the original goal. Use when the active `todowrite` exceeds 5 items, after ~20 tool calls, when the user says 'compact' / 'summarize so far' / 'we need to refocus', or when context usage is visibly heavy. Mirrors codex-rs/core/src/compact.rs::run_pre_sampling_compact." +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/blob/main/codex-rs/core/src/compact.rs +--- + +# 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. + +## 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). + +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. **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**: + ``` + +3. **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. +4. **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. +5. **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. + +## 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. + +## 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**: +- 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**: +- 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**: +- 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**: +- /repo/src/auth/idp.rs +- /repo/src/auth/callback.rs +- /repo/tests/auth/ + +**Blockers / open questions**: +- 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. +``` + +## 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/`). + +## 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")? +- [ ] 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? 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..982c787 --- /dev/null +++ b/plugins/antianqi/codex-harness-patterns/skills/parallel-fanout/SKILL.md @@ -0,0 +1,135 @@ +--- +name: parallel-fanout +description: "Decompose a clearly independent task into 2+ parallel sub-tasks and dispatch them with `task` in one round trip, then aggregate. Use when the user task can be split along a clean boundary (independent files, independent probes, independent analyses) and serial execution would take materially longer. Mirrors codex-rs FuturesUnordered fan-out in thread_manager.rs." +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/blob/main/codex-rs/core/src/thread_manager.rs +--- + +# Parallel Fan-Out + +When a task splits cleanly into 2+ independent sub-tasks, dispatch them in parallel and +aggregate. Skip when the sub-tasks have data dependencies or when serial execution is fast +enough that the overhead of fan-out is not worth it. + +## When to use + +Activate when **all** of the following hold: + +- The user task has 2+ **independent** sub-tasks. Independent means: sub-task B does not need + any output of sub-task A, and sub-task A does not need any output of sub-task B. +- Each sub-task is **bounded**: you can name what "done" looks like in one sentence. +- The estimated wall-clock time of serial execution is **at least 2×** the longest single + sub-task. If one sub-task dwarfs the others, fan-out buys you little. +- The user has not said "do it one by one" / "step by step" / "sequentially please". + +## When NOT to use + +- The sub-tasks share state (e.g. all read/modify the same file in conflicting ways). +- The sub-tasks need each other's intermediate output (e.g. test plan A depends on the refactor + in B). +- The total work is tiny (3 file edits); the orchestration overhead exceeds the savings. +- The user explicitly asked for serial work or a careful step-by-step walkthrough. +- You cannot articulate the boundary of each sub-task in one sentence. If you can't, you can't + safely parallelise it. + +## Process + +1. **State the decomposition first** — before any tool call, write a fenced block that names + the sub-tasks, their boundaries, and the aggregation step: + + ```markdown + ## Fan-out plan + + **Sub-task 1**: + **Sub-task 2**: + **Sub-task 3**: + + **Aggregation**: + **Stop conditions**: + ``` + +2. **Dispatch in parallel** with `task`. Use `run_in_background: true` for each so they overlap + in the same round trip. Pass a minimal-context brief to each — the original user request + plus the specific sub-task boundary, NOT the full history. + +3. **While waiting**, the orchestrating agent may draft the aggregation template (so the final + merge is a fill-in, not a re-derivation). + +4. **On all sub-tasks completing**: + - Verify each met its pass condition. + - If a sub-task drifted outside its boundary, **reject** and re-dispatch with a tighter + brief. Do not absorb the drift. + - If two sub-tasks produced conflicting facts (different numbers, different recommendations), + surface the conflict to the user **before** aggregating. Do not silently pick one. + +5. **Aggregate** into the agreed shape. Cite the source sub-task for each section so the user + can drill in. + +6. **Report** the wall-clock time saved if you have it (use timestamps from the sub-task + responses). This is how you earn the right to fan out again. + +## Output contract + +The user sees, in this order: + +- The Fan-out plan block (before any tool call). +- The list of dispatched sub-tasks (one line per `task` call). +- The pass/fail per sub-task. +- Any conflicts surfaced before aggregation. +- The aggregated result. +- (Optional) The wall-clock saving vs serial. + +## Example + +```markdown +## Fan-out plan + +**Sub-task 1**: Audit dependencies in /repo/server/Cargo.toml for known CVEs. + Pass condition: a table of {crate, version, advisory_id, severity}. +**Sub-task 2**: Audit dependencies in /repo/web/package.json for known CVEs. + Pass condition: a table of {package, version, advisory_id, severity}. +**Sub-task 3**: List license of every direct dependency in /repo/server and /repo/web. + Pass condition: a single table of {crate_or_package, license, copyleft_flag}. + +**Aggregation**: Combine into a single SECURITY-REPORT.md at the repo root. +**Stop conditions**: If sub-task 1 or 2 finds a critical CVE, surface immediately and do not +wait for sub-task 3. +``` + +Then: + +```text +> task(subagent=explore, run_in_background=true, + prompt="Audit /repo/server/Cargo.toml direct dependencies ...") +> task(subagent=explore, run_in_background=true, + prompt="Audit /repo/web/package.json direct dependencies ...") +> task(subagent=explore, run_in_background=true, + prompt="List licenses of /repo/server and /repo/web direct deps ...") +``` + +## Common pitfalls + +- **Do not fan out work that is too small.** A 200-line refactor is one task, not three. +- **Do not fan out work with hidden dependencies.** If sub-task 2 might need to read what + sub-task 1 wrote, that's serial. Don't pretend. +- **Do not over-brief sub-tasks.** "Refactor the auth subsystem" is not a sub-task brief; it + is the whole job. A sub-task brief names a file, a change, and a pass condition. +- **Do not under-brief.** "Look at the auth code" is not enough; the sub-agent will guess + wrong. Always include the file paths and the pass condition. +- **Do not aggregate silently.** If two sub-tasks disagree, the user must see the conflict. +- **Do not fan out > 5 sub-tasks.** Beyond 5, the aggregation step becomes a bottleneck and + context cost grows. For larger splits, ask the user first. + +## Verification checklist + +- [ ] Did you state the Fan-out plan block before any tool call? +- [ ] Is each sub-task truly independent (no shared state, no data flow between them)? +- [ ] Did you pass a minimal-context brief, not the full history? +- [ ] Did you use `run_in_background: true` so they overlap? +- [ ] Did you surface conflicts before aggregating? +- [ ] Did you cite the source sub-task for each section of the aggregation? +- [ ] Did the wall-clock savings actually justify the fan-out? 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..0df5934 --- /dev/null +++ b/plugins/antianqi/codex-harness-patterns/skills/plan-stream-emit/SKILL.md @@ -0,0 +1,135 @@ +--- +name: plan-stream-emit +description: "Before touching files on a non-trivial task, emit a structured plan as `todowrite` items and surface the plan to the user for early course-correction. Use when the user request is multi-step, has any ambiguity, or would take more than 3 tool calls to complete. Mirrors codex-rs `PlanUpdate` / `PlanDelta` events and the Op::PlanUpdate wire event." +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/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/tool-output-budget/SKILL.md b/plugins/antianqi/codex-harness-patterns/skills/tool-output-budget/SKILL.md new file mode 100644 index 0000000..804a929 --- /dev/null +++ b/plugins/antianqi/codex-harness-patterns/skills/tool-output-budget/SKILL.md @@ -0,0 +1,107 @@ +--- +name: tool-output-budget +description: "Truncate oversized tool output (large logs, JSON arrays, fetched HTML, minified code, noisy `cat` results) so it does not blow the agent's context window. Use when a tool returns more than ~3000 tokens, or contains any line longer than ~500 characters, or returns structured data the agent will only sample. Mirrors codex-rs/utils/output-truncation." +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/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? From 8a6c180be95294aa8871600aefa6c7fdab190ffb Mon Sep 17 00:00:00 2001 From: antianqi Date: Mon, 24 Aug 2026 00:22:51 +0800 Subject: [PATCH 04/49] Expand codex-harness-patterns: add review-mode, delegate-with-context, world-state-tracking, background-task (4 new Skills, 8 total) Adds four Skills that round out the long-running task toolkit: - review-mode switch to critic mode after finishing a chunk, produce a PASS / FIX / REDO verdict (mirrors EnteredReviewMode/ExitedReviewMode) - delegate-with-context write a minimal-context brief for task() instead of forwarding the full history (mirrors InterAgentCommunication / CollabAgentSpawn) - world-state-tracking persist a structured state file that survives context compaction (mirrors WorldState in core/src/context/world_state.rs) - background-task run long-running commands in the background with a log file, poll on later turns (mirrors unified_exec / CleanBackgroundTerminals) Manifest bumped to 0.2.0; README and plugin.json keywords updated to cover the full 8-Skill surface. Validation: npm run check still passes for this plugin (OK plugin antianqi/codex-harness-patterns). --- .../antianqi/codex-harness-patterns/README.md | 38 +++- .../codex-harness-patterns/plugin.json | 9 +- .../skills/background-task/SKILL.md | 117 +++++++++++ .../skills/delegate-with-context/SKILL.md | 149 ++++++++++++++ .../skills/review-mode/SKILL.md | 143 +++++++++++++ .../skills/world-state-tracking/SKILL.md | 189 ++++++++++++++++++ 6 files changed, 633 insertions(+), 12 deletions(-) create mode 100644 plugins/antianqi/codex-harness-patterns/skills/background-task/SKILL.md create mode 100644 plugins/antianqi/codex-harness-patterns/skills/delegate-with-context/SKILL.md create mode 100644 plugins/antianqi/codex-harness-patterns/skills/review-mode/SKILL.md create mode 100644 plugins/antianqi/codex-harness-patterns/skills/world-state-tracking/SKILL.md diff --git a/plugins/antianqi/codex-harness-patterns/README.md b/plugins/antianqi/codex-harness-patterns/README.md index 45dbaa1..a8cac4c 100644 --- a/plugins/antianqi/codex-harness-patterns/README.md +++ b/plugins/antianqi/codex-harness-patterns/README.md @@ -2,7 +2,8 @@ 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, or stalling on serial work. +multi-step tasks without losing focus, blowing its token budget, stalling on serial work, +shipping unverified changes, or burning context on bad sub-agent briefs. ## The problem @@ -16,13 +17,21 @@ Long agentic sessions fail for predictable reasons: finish in one round trip. - **No plan** — the model dives into a complex task without first surfacing a structured plan, so the user cannot course-correct early. +- **No review** — the model writes code, says "done", and ships a defect the user has to find. +- **Bloated sub-agent briefs** — the model dumps the full conversation history into a `task` + call, paying the token cost twice. +- **No shared state** — long tasks have no persistent ground truth that survives context + compaction, so the model keeps re-deriving "where are we?". +- **Foreground blocks** — the model `bash`es a 5-minute build, blocks the conversation, and + times out. OpenAI's Codex harness solves each of these with specific code (see [`codex-rs/core/src/compact.rs`](https://github.com/openai/codex/blob/main/codex-rs/core/src/compact.rs), [`utils/output-truncation/`](https://github.com/openai/codex/tree/main/codex-rs/utils/output-truncation), -[`session/turn.rs::run_turn`](https://github.com/openai/codex/blob/main/codex-rs/core/src/session/turn.rs)) +[`session/turn.rs::run_turn`](https://github.com/openai/codex/blob/main/codex-rs/core/src/session/turn.rs), +[`context/world_state.rs`](https://github.com/openai/codex/blob/main/codex-rs/core/src/context/world_state.rs)) and reports a 3× score lift on ARC-AGI-3 with the same model, just by changing the harness. -This Plugin packages those four patterns as portable Skills. +This Plugin packages those patterns as portable Skills. ## Try it @@ -36,6 +45,12 @@ Install from `/plugins` → **Local**, then ask any of: "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." ``` **Expected result**: the agent picks the right Skill, follows the documented process, and produces @@ -44,7 +59,7 @@ contract and example). ## What this Plugin adds -Four Skills, all Skill-only (no MCP server, no network access): +Eight Skills, all Skill-only (no MCP server, no network access): | Skill | When to activate | |---|---| @@ -52,6 +67,10 @@ Four Skills, all Skill-only (no MCP server, no network access): | `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. | | `parallel-fanout` | The user task is clearly decomposable into 2+ independent sub-tasks (independent files, independent probes, independent analyses). | | `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. | +| `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. | +| `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. | +| `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. | +| `background-task` | A command is expected to take > 30 seconds, or the user wants a long-running process to coexist with ongoing work. | ## Requirements @@ -66,11 +85,12 @@ Four Skills, all Skill-only (no MCP server, no network access): - **Read-only by default** (these Skills only change how the agent shapes its own output and tool calls). - **No file modification outside the agent's existing write surface.** The Skills may instruct - the agent to use `write` / `edit` / `bash` to persist a compact summary or a plan file, but only - on paths the user already authorised through the active session. -- **No sub-agent launch without user intent.** `parallel-fanout` instructs the agent to use - `task` for fan-out, but only when the user task is independently decomposable. The agent must - still justify the decomposition in the plan and stop if the user says "do it one by one". + the agent to use `write` / `edit` / `bash` to persist a compact summary, a plan file, or a + world-state file, but only on paths the user already authorised through the active session. +- **No sub-agent launch without user intent.** `parallel-fanout` and `delegate-with-context` + instruct the agent to use `task` for fan-out / delegation, but only when the user task is + independently decomposable. The agent must still justify the decomposition in the plan + and stop if the user says "do it one by one". ## Data and network diff --git a/plugins/antianqi/codex-harness-patterns/plugin.json b/plugins/antianqi/codex-harness-patterns/plugin.json index 305e54a..6158203 100644 --- a/plugins/antianqi/codex-harness-patterns/plugin.json +++ b/plugins/antianqi/codex-harness-patterns/plugin.json @@ -1,8 +1,8 @@ { "$schema": "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json", "name": "codex-harness-patterns", - "version": "0.1.0", - "description": "Long-running task patterns distilled from OpenAI Codex harness v0.149.0 — tool output budgeting, context pressure compaction, parallel sub-agent fan-out, and structured plan streaming. Activates when an agent must manage token budget, decompose work, or sustain multi-step tasks without losing focus. Inspired by codex-rs/core/src/compact.rs, run_turn, and Op/EventMsg protocol.", + "version": "0.2.0", + "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, and background task management. Activates when an agent must manage token budget, decompose work, sustain multi-step tasks without losing focus, or coordinate sub-agents. Inspired by codex-rs/core/src/compact.rs, run_turn, Op/EventMsg protocol, and the InterAgentCommunication / CollabAgent / WorldState sub-systems.", "author": { "name": "antianqi", "url": "https://github.com/antianqi" @@ -18,6 +18,9 @@ "context-compaction", "sub-agent", "task-planning", - "long-running" + "long-running", + "review", + "world-state", + "background-task" ] } 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..848a51b --- /dev/null +++ b/plugins/antianqi/codex-harness-patterns/skills/background-task/SKILL.md @@ -0,0 +1,117 @@ +--- +name: background-task +description: "Run a long-running command (dev server, build, watcher, test loop, file sync) as a background task that the agent can poll, steer, and shut down, instead of blocking the conversation on it. Use when a command is expected to take > 30 seconds, when the user wants to keep talking while it runs, when you need to start something and then check on it later in the same session, or when an earlier foreground call already failed with a timeout. Mirrors codex-rs CleanBackgroundTerminals and unified_exec in core/src/unified_exec/." +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/blob/main/codex-rs/core/src/unified_exec/ +--- + +# Background Task + +Run a long-running command as a background task and return control to the agent immediately. +Poll, steer, or kill it on later turns. Do not block the conversation on a 10-minute build. + +## When to use + +Activate when **any** of these is true: + +- The command is expected to take > 30 seconds (a full `cargo test`, a `vite dev` server, a + `webpack --watch`). +- The user has said "start the dev server in the background" / "kick off the build" / "let + me know when it's done". +- You need to run multiple long commands and want them to overlap. +- A previous foreground call already hit a timeout. +- The command is meant to run indefinitely (`watch`, `serve`, `tail -f`) and you only need + to read its output on demand. + +## When NOT to use + +- The command is short (< 30 seconds). Just run it in the foreground. +- The command's output is the entire point (a `curl` whose body you must inspect). Read it + in one shot, not as a stream. +- The command is destructive and you need to see the result before continuing (e.g. + `rm -rf`). Run it foreground, see the exit code, then decide. + +## Process + +1. **State the start plan** in one line before launching: "Starting `npm run dev` in the + background (expected ~5s to be ready, polling every 10s)." +2. **Launch with `run_in_background: true`** (or your harness's equivalent). Pick a + descriptive `task_name` so the user can recognise it: `dev-server`, `cargo-test`, + `vite-watch`. Not `task1`. +3. **On launch, do not block.** Return immediately to whatever the user asked next. Do + not poll in the same turn unless the user explicitly asked you to wait. +4. **On a later turn (or when the user asks "is it ready?"):** + - `read` the output buffer (or `tail` the log file if you wrote one). + - If still running, report progress and continue. + - If exited, report the exit code and a one-line summary of the last output. +5. **On user request to stop** (or when the task is no longer needed): kill the background + task. Confirm with the user before killing anything they explicitly started. +6. **At end of session / on `context-pressure-compact`:** list the running background tasks + in the state file so they survive the compaction. + +## Output contract + +The user sees, in this order: + +- One-line "starting X in background" plan. +- The launch invocation (one line, with the `task_name`). +- A short status line on every later turn that touches the task: "X: running, 3124 lines + of output so far" / "X: exited 0, last line '...'" / "X: still running, no output yet". +- A clean "stopped X" when killed. + +## Example + +```text +> bash(task_name="cargo-test", run_in_background=true, + prompt="cd /repo && cargo test --workspace 2>&1 | tee /tmp/cargo-test.log") +launched cargo-test (id: bt-7a3f); returning to user + +[user asks "how's the test run?" two minutes later] + +> read offset=0 limit=200 /tmp/cargo-test.log +cargo-test: running, 1234 lines of output so far + ✓ 23 passed in 0.4s + ✓ 7 passed in 0.2s + … + running 12 of 240 tests (auth::session::rotate) + no failures yet +``` + +```text +[user asks "stop the test run, I want to fix the failing one manually"] + +> bash(task_name="cargo-test", action="kill") +stopped cargo-test; last output preserved at /tmp/cargo-test.log +``` + +## Common pitfalls + +- **Do not launch with `run_in_background: true` and then immediately poll in the same + turn.** That defeats the purpose. Launch and return; poll on a later turn or when the + user asks. +- **Do not use generic `task_name` values.** `dev-server` is good, `task1` is bad — the + user will not know which task is which after the second background task. +- **Do not buffer the entire output in the context.** If the task writes to a log file, + `read` with `offset` + `limit` or `tail`. Do not `cat` the whole thing. +- **Do not assume the task is healthy just because it is running.** A 5-minute `cargo test` + with no new output is hung, not progressing. Check the log. +- **Do not forget to kill.** Background tasks that the user no longer needs are silent + resource leaks. List them in the state file and clean up on session end. +- **Do not start a background task that writes to stdout the agent must read in real time.** + Use a log file. Stdout from a backgrounded process is awkward to recover reliably. +- **Do not block the conversation on the task's first output.** The first output is often + not informative (build setup, server starting, test warming up). + +## Verification checklist + +- [ ] Did you state the start plan in one line? +- [ ] Did you use `run_in_background: true` (or equivalent) and a descriptive `task_name`? +- [ ] Did you return to the user immediately, not block on the first output? +- [ ] Is the task's output going to a log file (so polling is cheap)? +- [ ] On later turns, is the status report one line with exit code + last line of output? +- [ ] On stop, did you confirm with the user before killing? +- [ ] Are running background tasks listed in the state file for compaction survival? 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..fd39708 --- /dev/null +++ b/plugins/antianqi/codex-harness-patterns/skills/delegate-with-context/SKILL.md @@ -0,0 +1,149 @@ +--- +name: delegate-with-context +description: "When delegating a sub-task to another agent (via `task`), prepare a minimal-context brief instead of dumping the full conversation history. Use when handing off a sub-task boundary, when a sub-agent needs the user's goal + the specific boundary + the pass condition + the minimal inputs, and when the conversation history is large enough that forwarding it all would waste tokens. Mirrors codex-rs InterAgentCommunication in Op / CollabAgentSpawnBegin." +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/blob/main/codex-rs/protocol/src/protocol.rs (Op::InterAgentCommunication, CollabAgentSpawnBegin/End) +--- + +# Delegate With Context + +When you spawn a sub-agent (`task` or equivalent), the brief you pass is the only thing it sees. +A bad brief makes the sub-agent re-derive the whole conversation; a good brief gives it exactly +what it needs to do its job and nothing more. + +This Skill is the inverse of "pass the full history" — the full history is the most expensive +context you can give a sub-agent, and it is rarely what the sub-agent needs. + +## 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. +- The full conversation history is > 30 turns or contains large tool outputs the sub-agent + does not need. +- The sub-task has a clear boundary (file, function, doc, test) that can be described in one + sentence. +- You find yourself wanting to write "see the conversation above" — that is the trigger to + stop and write an actual brief. + +## When NOT to use + +- The sub-agent is a simple one-shot lookup that needs verbatim context (rare; usually a + `read` or `grep` is faster than a sub-agent). +- The sub-task boundary is fuzzy. If you cannot name the boundary, you cannot brief it — + decompose first, then delegate. +- The work is so small that the brief would be longer than just doing it. + +## Process + +1. **Write the brief as a fenced block** in this exact shape, **before** calling `task`: + + ```markdown + ## Sub-task brief + + **Goal** (one sentence, in the user's own words if possible): + <...> + + **Boundary** (what is in scope, what is out of scope): + - In: <...> + - Out: <...> + + **Inputs** (only what the sub-agent needs to read; absolute paths): + - /path/to/file.rs (function `foo`) + - /path/to/spec.md (section 3.2 only) + - + + **Pass condition** (one checkable sentence): + <...> + + **Output shape** (what the sub-agent should return): + - A patch, a report, a single sentence, a JSON object — be specific. + - If returning code, name the file path the patch should land in. + + **Constraints** (what NOT to do, to save round trips): + - Do not refactor adjacent code. + - Do not change the public API. + - Do not introduce new dependencies. + - + ``` + +2. **Call `task` with the brief as the prompt.** The full conversation history is *not* in + the prompt; the brief is. +3. **Verify the brief round-tripped.** Read the sub-agent's first response. If it is solving + the wrong problem, your brief failed — do not let it finish. Stop and re-brief. +4. **If the sub-agent needs more context mid-task**, send a follow-up brief in the same + shape, not the original full history. +5. **On return, validate against the pass condition.** If unmet, re-dispatch with a tighter + brief; do not patch the result yourself unless the fix is trivial. + +## Output contract + +The user sees, in this order: + +- The Sub-task brief block (before the `task` call). +- The `task` invocation (one line). +- The sub-agent's first sentence (or its pass/fail against the pass condition). +- (If failed) the re-brief, not a silent retry. + +## Example + +```markdown +## Sub-task brief + +**Goal**: Add a single function `format_currency(amount: f64, currency: &str) -> String` to +`src/money.rs` that formats USD with two decimals, EUR with symbol suffix, JPY with no decimals. + +**Boundary**: +- In: one new function + 4 unit tests +- Out: refactoring `money.rs`, changing existing callers, adding a new file + +**Inputs**: +- /repo/src/money.rs (read top of file to see the existing style) + +**Pass condition**: +- `cargo test money::` green +- `format_currency(1234.5, "USD") == "$1,234.50"` +- `format_currency(1234.5, "EUR") == "1,234.50 €"` +- `format_currency(1234.0, "JPY") == "¥1,234"` + +**Output shape**: a unified diff against `/repo/src/money.rs`. + +**Constraints**: +- No new dependencies (no `rust_decimal`, `num-format`, etc.) +- Match the existing function signature style in `money.rs` +``` + +Then: + +```text +> task(subagent=explore, + prompt="") +``` + +## Common pitfalls + +- **Do not pass the full conversation history as context.** That is the failure mode this Skill + exists to prevent. Pass the brief. +- **Do not write a brief that says "see above".** The sub-agent does not have "above". +- **Do not omit the pass condition.** Without it, the sub-agent picks its own definition of + done, which is rarely yours. +- **Do not omit the constraints.** "Don't refactor adjacent code" saves a 3-message ping-pong. +- **Do not over-brief.** A 200-line brief for a one-function change is itself a token waste. +- **Do not under-brief.** "Look at the auth code" is a wish, not a brief. +- **Do not brief a sub-task boundary that is fuzzy.** Decompose first (`plan-stream-emit`), + then brief the resulting steps. + +## Verification checklist + +- [ ] Did you write the Sub-task brief block before calling `task`? +- [ ] Is the goal one sentence in the user's voice? +- [ ] Is the boundary explicit (in / out)? +- [ ] Are the inputs absolute paths, not "look around the repo"? +- [ ] Is the pass condition one checkable sentence? +- [ ] Is the output shape specific (patch / report / JSON)? +- [ ] Did you list the constraints to head off re-work? +- [ ] Did the sub-agent's first response show it understood the brief? 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..3cf39ea --- /dev/null +++ b/plugins/antianqi/codex-harness-patterns/skills/review-mode/SKILL.md @@ -0,0 +1,143 @@ +--- +name: review-mode +description: "After completing a non-trivial chunk of work (a function, a file, a feature, a config change), switch to a critical-reviewer mode and verify the work before declaring it done. Use when a sub-task boundary is reached, when the user says 'review this' / 'double-check' / 'is this right', or before reporting 'done' on anything the user will rely on. Mirrors codex-rs EnteredReviewMode / ExitedReviewMode events in protocol/src/protocol.rs." +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/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/world-state-tracking/SKILL.md b/plugins/antianqi/codex-harness-patterns/skills/world-state-tracking/SKILL.md new file mode 100644 index 0000000..01f5746 --- /dev/null +++ b/plugins/antianqi/codex-harness-patterns/skills/world-state-tracking/SKILL.md @@ -0,0 +1,189 @@ +--- +name: world-state-tracking +description: "Track the running state of a long task (goal, decisions, blockers, next step, key paths) in a single dedicated file that survives context compaction. Use when the task is long enough that `todowrite` alone is too thin, when the user keeps referring to 'where we are', when the agent has lost the thread, or at every `context-pressure-compact` boundary. Mirrors codex-rs `WorldState` struct in core/src/context/world_state.rs." +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/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? From fdbffcc9bc35e4842553ed0a717c7821d868585c Mon Sep 17 00:00:00 2001 From: antianqi Date: Mon, 24 Aug 2026 00:45:07 +0800 Subject: [PATCH 05/49] =?UTF-8?q?v0.3.0:=20add=202=20Skills=20(goal-persis?= =?UTF-8?q?tence,=20model-router)=20=E2=80=94=2010=20Skills=20total?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds two Skills that close the long-running task loop: - goal-persistence P-14 SetThreadMemoryMode + ThreadGoalUpdated (north-star goal file, drift self-test before non-trivial tool calls, survives compactions) - model-router P-07 model-provider-info + models-manager (classify sub-task as cheap/medium/main, pass model_config_id explicitly, no silent defaults) Manifest bumped to 0.3.0; README table now lists all 10 Skills. Validation: npm run check still passes for this plugin (OK plugin antianqi/codex-harness-patterns). --- .../antianqi/codex-harness-patterns/README.md | 36 +++- .../codex-harness-patterns/plugin.json | 8 +- .../skills/goal-persistence/SKILL.md | 194 ++++++++++++++++++ .../skills/model-router/SKILL.md | 143 +++++++++++++ 4 files changed, 369 insertions(+), 12 deletions(-) create mode 100644 plugins/antianqi/codex-harness-patterns/skills/goal-persistence/SKILL.md create mode 100644 plugins/antianqi/codex-harness-patterns/skills/model-router/SKILL.md diff --git a/plugins/antianqi/codex-harness-patterns/README.md b/plugins/antianqi/codex-harness-patterns/README.md index a8cac4c..032aa51 100644 --- a/plugins/antianqi/codex-harness-patterns/README.md +++ b/plugins/antianqi/codex-harness-patterns/README.md @@ -3,7 +3,8 @@ 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, or burning context on bad sub-agent briefs. +shipping unverified changes, burning context on bad sub-agent briefs, drifting from the +original goal, or paying main-model prices for cheap-model work. ## The problem @@ -24,12 +25,17 @@ Long agentic sessions fail for predictable reasons: compaction, so the model keeps re-deriving "where are we?". - **Foreground blocks** — the model `bash`es a 5-minute build, blocks the conversation, and times out. +- **Goal drift** — the original user request gets silently replaced by an inferred goal, and + the agent ends up doing a side quest with confident justification. +- **Model over-spend** — the agent uses the main model for routine lookups and transforms + that a cheap model could handle in a fraction of the time and cost. OpenAI's Codex harness solves each of these with specific code (see [`codex-rs/core/src/compact.rs`](https://github.com/openai/codex/blob/main/codex-rs/core/src/compact.rs), [`utils/output-truncation/`](https://github.com/openai/codex/tree/main/codex-rs/utils/output-truncation), [`session/turn.rs::run_turn`](https://github.com/openai/codex/blob/main/codex-rs/core/src/session/turn.rs), -[`context/world_state.rs`](https://github.com/openai/codex/blob/main/codex-rs/core/src/context/world_state.rs)) +[`context/world_state.rs`](https://github.com/openai/codex/blob/main/codex-rs/core/src/context/world_state.rs), +[`model-provider-info/`](https://github.com/openai/codex/tree/main/codex-rs/model-provider-info)) and reports a 3× score lift on ARC-AGI-3 with the same model, just by changing the harness. This Plugin packages those patterns as portable Skills. @@ -51,6 +57,11 @@ Install from `/plugins` → **Local**, then ask any of: "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." ``` **Expected result**: the agent picks the right Skill, follows the documented process, and produces @@ -59,7 +70,7 @@ contract and example). ## What this Plugin adds -Eight Skills, all Skill-only (no MCP server, no network access): +Ten Skills, all Skill-only (no MCP server, no network access): | Skill | When to activate | |---|---| @@ -71,6 +82,8 @@ Eight Skills, all Skill-only (no MCP server, no network access): | `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. | | `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. | | `background-task` | A command is expected to take > 30 seconds, or the user wants a long-running process to coexist with ongoing work. | +| `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). | +| `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. | ## Requirements @@ -85,12 +98,17 @@ Eight Skills, all Skill-only (no MCP server, no network access): - **Read-only by default** (these Skills only change how the agent shapes its own output and tool calls). - **No file modification outside the agent's existing write surface.** The Skills may instruct - the agent to use `write` / `edit` / `bash` to persist a compact summary, a plan file, or a - world-state file, but only on paths the user already authorised through the active session. -- **No sub-agent launch without user intent.** `parallel-fanout` and `delegate-with-context` - instruct the agent to use `task` for fan-out / delegation, but only when the user task is - independently decomposable. The agent must still justify the decomposition in the plan - and stop if the user says "do it one by one". + the agent to use `write` / `edit` / `bash` to persist a compact summary, a plan file, a + world-state file, or a goal file, but only on paths the user already authorised through the + active session. +- **No sub-agent launch without user intent.** `parallel-fanout`, `delegate-with-context`, and + `model-router` instruct the agent to use `task` for fan-out / delegation, but only when the + user task is independently decomposable. The agent must still justify the decomposition in + the plan and stop if the user says "do it one by one". +- **No model switching that the harness does not support.** `model-router` only works if the + underlying `task` tool exposes `model_config_id` (or equivalent). If the harness does not + support model routing, the Skill degrades to "classify the sub-task" and the model choice + follows whatever default the harness provides. ## Data and network diff --git a/plugins/antianqi/codex-harness-patterns/plugin.json b/plugins/antianqi/codex-harness-patterns/plugin.json index 6158203..5d82ce0 100644 --- a/plugins/antianqi/codex-harness-patterns/plugin.json +++ b/plugins/antianqi/codex-harness-patterns/plugin.json @@ -1,8 +1,8 @@ { "$schema": "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json", "name": "codex-harness-patterns", - "version": "0.2.0", - "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, and background task management. Activates when an agent must manage token budget, decompose work, sustain multi-step tasks without losing focus, or coordinate sub-agents. Inspired by codex-rs/core/src/compact.rs, run_turn, Op/EventMsg protocol, and the InterAgentCommunication / CollabAgent / WorldState sub-systems.", + "version": "0.3.0", + "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, and per-sub-task model routing. Activates when an agent must manage token budget, decompose work, sustain multi-step tasks without losing focus, coordinate sub-agents, or pick the right model for the job. Inspired by codex-rs/core/src/compact.rs, run_turn, Op/EventMsg protocol, InterAgentCommunication, WorldState, model-provider-info, and the models-manager sub-systems.", "author": { "name": "antianqi", "url": "https://github.com/antianqi" @@ -21,6 +21,8 @@ "long-running", "review", "world-state", - "background-task" + "background-task", + "goal", + "model-routing" ] } 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..7ba36fd --- /dev/null +++ b/plugins/antianqi/codex-harness-patterns/skills/goal-persistence/SKILL.md @@ -0,0 +1,194 @@ +--- +name: goal-persistence +description: "Maintain an explicit north-star goal for the whole thread that survives compactions and detects drift. Use at the start of any non-trivial task (one-time set), after every user redirection (one-time update), and at every `context-pressure-compact` boundary (one-line alignment check). Mirrors codex-rs `Op::SetThreadMemoryMode` + `EventMsg::ThreadGoalUpdated` in protocol/src/protocol.rs." +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/blob/main/codex-rs/protocol/src/protocol.rs (SetThreadMemoryMode, ThreadGoalUpdatedEvent) +--- + +# 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*. + +## 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"). + +## 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. **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. + +## 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 ". + +## Example + +```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 let the goal silently drift via tool calls.** The whole point of this + Skill is that drift is *visible*, not hidden. + +## Verification checklist + +- [ ] Did you pick a single, predictable path for the goal file? +- [ ] Is the goal file under ~40 lines? +- [ ] Does it have all 6 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? +- [ ] On "done", did you mark the goal as achieved in the file (audit trail)? 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..d776956 --- /dev/null +++ b/plugins/antianqi/codex-harness-patterns/skills/model-router/SKILL.md @@ -0,0 +1,143 @@ +--- +name: model-router +description: "Before delegating a sub-task (via `task`), assess the sub-task's complexity and pick the model config that matches it: cheap model for routine lookups, main model for complex work. Use every time you call `task` and the sub-task is non-trivial, and any time you are about to spend the main model on work a cheap model could do. Mirrors codex-rs `model-provider-info` + `models-manager` + the routing layer that lets sub-agents run on cheaper models." +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/blob/main/codex-rs/model-provider-info/ and codex-rs/models-manager/ +--- + +# 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. + +This Skill codifies that routing: before every `task` call, classify the sub-task and pick +the right `model_config_id`. The savings are not theoretical — the same model router that +gave Codex a 6× token reduction on context compaction works the same way on delegation. + +## When to use + +Activate when **any** of these is true: + +- You are about to call `task` and the sub-task 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 a stronger model would + help, or whether the brief was just bad. +- A batch of N similar sub-tasks is about to run; one model call to classify them + first, then route each. + +## 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. A cheap model with no context will fail — route to main. +- The user explicitly said "use the main model for this" or "don't downgrade the + model". + +## Process + +1. **Classify the sub-task** into one of three tiers, before writing the brief: + + | 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. **Pick the `model_config_id`** for the tier: + + - `cheap` → the cheapest model the harness exposes (often a haiku-class or local model). + - `medium` → the same model family as `main` but at the lowest reasoning effort, or a + mid-tier model. + - `main` → the user's main model at its default reasoning effort. + + The exact names depend on the harness; the principle is: cheapest that can succeed. + +3. **Pass the model config explicitly** in the `task` call. Do not rely on default + routing — the default is the main model, which is the wrong answer for `cheap` and + `medium` tiers. + +4. **State the tier in the sub-task brief** so a human reviewer can see why you picked + that model: + + ```markdown + ## Sub-task brief + ... + + **Model tier**: cheap (reformat-only, no judgement needed) + ``` + +5. **If the sub-task returns a "I can't do this"** (cheap model could not satisfy the + brief), do not silently retry on the same tier. Re-classify up, and explain to the + user *why* the sub-task was harder than the tier suggested. + +6. **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 `task` call: the tier and the chosen `model_config_id` (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. + +## Example + +```text +[planning] 1 cheap call: list all *.rs files in /repo/src/auth/ that import `tokio::sync::Mutex`. + — tier: cheap (deterministic glob + grep, no judgement) + — model_config_id: anthropic-haiku-3 + +[execution] 1 medium call: refactor auth/callback.rs to extract the SAML response parser. + — tier: medium (multi-step refactor, brief is the spec) + — model_config_id: anthropic-sonnet-4 with reasoning_effort=low + +[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) + — model_config_id: anthropic-sonnet-4 with reasoning_effort=high + +[aggregation] spend summary: 1 cheap / 1 medium / 1 main. 78% of the work was on the + main call; the other two ran in <2s. +``` + +## 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. If in doubt, tier up. +- **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 skip the explicit `model_config_id`.** The default in most harnesses is + the main model. If you do not pass a config, you have routed to `main`. +- **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 + move up a tier, 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 writing the brief? +- [ ] Did you pass the `model_config_id` explicitly in the `task` call? +- [ ] Did you state the tier in the sub-task brief? +- [ ] 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? +- [ ] Did the cheap-model portion actually run on the cheap model (not silently + re-routed to main)? +- [ ] Did the savings justify the routing decision (i.e. was the work appropriate for + the tier you picked)? From c2422f29feadc2d7bff54cc80ee995aece2ea02b Mon Sep 17 00:00:00 2001 From: antianqi Date: Mon, 24 Aug 2026 00:57:24 +0800 Subject: [PATCH 06/49] =?UTF-8?q?v0.4.0:=2012=20Skills=20total=20=E2=80=94?= =?UTF-8?q?=20add=20completion-audit=20+=20fork-context-decision;=20upgrad?= =?UTF-8?q?e=20goal-persistence=20+=20parallel-fanout=20to=20v1.0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit New Skills (2): - completion-audit P-22 continuation template completion-audit section (derive requirements, identify authoritative evidence, verify each, only declare done on all-✅) - fork-context-decision P-20 fork_turns semantics (all / N / none — pick explicitly, not by default) Skill upgrades to v1.0 (2): - goal-persistence + completion-audit and blocked-audit sections + token-budget reporting rule + 'treat completion as unproven' alignment - parallel-fanout + explicit-spawn principle (P-20: opt-in, not auto) + max_concurrency awareness + cross-references to fork-context-decision and delegate-with-context + completion-audit on aggregation before done Total Skills: 12. Manifest bumped to 0.4.0. Validation: npm run check still passes for this plugin (OK plugin antianqi/codex-harness-patterns). --- .../antianqi/codex-harness-patterns/README.md | 69 +++++--- .../codex-harness-patterns/plugin.json | 8 +- .../skills/completion-audit/SKILL.md | 154 ++++++++++++++++++ .../skills/fork-context-decision/SKILL.md | 140 ++++++++++++++++ .../skills/goal-persistence/SKILL.md | 92 +++++++++-- .../skills/parallel-fanout/SKILL.md | 97 ++++++++--- 6 files changed, 509 insertions(+), 51 deletions(-) create mode 100644 plugins/antianqi/codex-harness-patterns/skills/completion-audit/SKILL.md create mode 100644 plugins/antianqi/codex-harness-patterns/skills/fork-context-decision/SKILL.md diff --git a/plugins/antianqi/codex-harness-patterns/README.md b/plugins/antianqi/codex-harness-patterns/README.md index 032aa51..5e61b88 100644 --- a/plugins/antianqi/codex-harness-patterns/README.md +++ b/plugins/antianqi/codex-harness-patterns/README.md @@ -19,6 +19,7 @@ Long agentic sessions fail for predictable reasons: - **No plan** — the model dives into a complex task without first surfacing a structured plan, so the user cannot course-correct early. - **No review** — the model writes code, says "done", and ships a defect the user has to find. +- **No proof of done** — the model marks a task complete from memory, not from evidence. - **Bloated sub-agent briefs** — the model dumps the full conversation history into a `task` call, paying the token cost twice. - **No shared state** — long tasks have no persistent ground truth that survives context @@ -29,12 +30,15 @@ Long agentic sessions fail for predictable reasons: the agent ends up doing a side quest with confident justification. - **Model over-spend** — the agent uses the main model for routine lookups and transforms that a cheap model could handle in a fraction of the time and cost. +- **Sub-agent context over-spend** — the agent gives every sub-agent the full history when a + small brief would do. OpenAI's Codex harness solves each of these with specific code (see [`codex-rs/core/src/compact.rs`](https://github.com/openai/codex/blob/main/codex-rs/core/src/compact.rs), [`utils/output-truncation/`](https://github.com/openai/codex/tree/main/codex-rs/utils/output-truncation), [`session/turn.rs::run_turn`](https://github.com/openai/codex/blob/main/codex-rs/core/src/session/turn.rs), [`context/world_state.rs`](https://github.com/openai/codex/blob/main/codex-rs/core/src/context/world_state.rs), +[`ext/goal/templates/goals/continuation.md`](https://github.com/openai/codex/blob/main/codex-rs/ext/goal/templates/goals/continuation.md), [`model-provider-info/`](https://github.com/openai/codex/tree/main/codex-rs/model-provider-info)) and reports a 3× score lift on ARC-AGI-3 with the same model, just by changing the harness. This Plugin packages those patterns as portable Skills. @@ -62,28 +66,54 @@ Install from `/plugins` → **Local**, then ask any of: 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?" ``` **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 - -Ten Skills, all Skill-only (no MCP server, no network access): - -| Skill | When to activate | -|---|---| -| `tool-output-budget` | A tool returns output you suspect is too large to keep verbatim (large logs, JSON, fetched HTML, minified files). | -| `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. | -| `parallel-fanout` | The user task is clearly decomposable into 2+ independent sub-tasks (independent files, independent probes, independent analyses). | -| `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. | -| `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. | -| `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. | -| `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. | -| `background-task` | A command is expected to take > 30 seconds, or the user wants a long-running process to coexist with ongoing work. | -| `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). | -| `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. | +## What this Plugin adds (v0.4.0, 12 Skills) + +Twelve Skills, all Skill-only (no MCP server, no network access): + +| # | Skill | When to activate | v0.4.0 | +|---|---|---|---| +| 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 | +| 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 | +| 3 | `parallel-fanout` | The user task is clearly decomposable into 2+ independent sub-tasks (independent files, independent probes, independent analyses). | v0.1.0 → **v1.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 | +| 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 | +| 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 | +| 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 | +| 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 | +| 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** | +| 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 | +| 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 (new)** | +| 12 | `fork-context-decision` | About to call `task` to hand off a sub-task. Decides how much parent context to give the sub-agent via the `fork_turns` parameter. | **v0.4.0 (new)** | + +## v0.4.0 changelog + +### Added + +- `completion-audit` Skill — derive requirements, identify authoritative evidence, verify each, + only declare done when every requirement has its own ✅. Mirrors the completion-audit section + of the Codex goal continuation template. +- `fork-context-decision` Skill — pick `all` / `N` / `none` for `fork_turns` explicitly, not + by default. Mirrors the `fork_turns` semantics in Codex's V2 multi-agent protocol. + +### Updated + +- `goal-persistence` v1.0 — incorporated the 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. +- `parallel-fanout` v1.0 — added explicit-spawn principle (P-20: spawn is opt-in, not auto). + Added `max_concurrency` awareness. Cross-referenced `fork-context-decision` and + `delegate-with-context`. Added `completion-audit` on the aggregation before declaring + done. ## Requirements @@ -102,9 +132,10 @@ Ten Skills, all Skill-only (no MCP server, no network access): world-state file, or a goal file, but only on paths the user already authorised through the active session. - **No sub-agent launch without user intent.** `parallel-fanout`, `delegate-with-context`, and - `model-router` instruct the agent to use `task` for fan-out / delegation, but only when the - user task is independently decomposable. The agent must still justify the decomposition in - the plan and stop if the user says "do it one by one". + `fork-context-decision` instruct the agent to use `task` for fan-out / delegation, but only + when the user task is independently decomposable **and** the user has opted in to + multi-agent work. The agent must still justify the decomposition in the plan and stop if + the user says "do it one by one". - **No model switching that the harness does not support.** `model-router` only works if the underlying `task` tool exposes `model_config_id` (or equivalent). If the harness does not support model routing, the Skill degrades to "classify the sub-task" and the model choice diff --git a/plugins/antianqi/codex-harness-patterns/plugin.json b/plugins/antianqi/codex-harness-patterns/plugin.json index 5d82ce0..424dea3 100644 --- a/plugins/antianqi/codex-harness-patterns/plugin.json +++ b/plugins/antianqi/codex-harness-patterns/plugin.json @@ -1,8 +1,8 @@ { "$schema": "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json", "name": "codex-harness-patterns", - "version": "0.3.0", - "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, and per-sub-task model routing. Activates when an agent must manage token budget, decompose work, sustain multi-step tasks without losing focus, coordinate sub-agents, or pick the right model for the job. Inspired by codex-rs/core/src/compact.rs, run_turn, Op/EventMsg protocol, InterAgentCommunication, WorldState, model-provider-info, and the models-manager sub-systems.", + "version": "0.4.0", + "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, and fork-context decision. 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, or prove that a non-trivial task is actually done. Inspired by codex-rs/core/src/compact.rs, run_turn, Op/EventMsg protocol, InterAgentCommunication, WorldState, ext/goal continuation template, model-provider-info, and the models-manager sub-systems.", "author": { "name": "antianqi", "url": "https://github.com/antianqi" @@ -23,6 +23,8 @@ "world-state", "background-task", "goal", - "model-routing" + "model-routing", + "completion-audit", + "fork-context" ] } 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..10c33cc --- /dev/null +++ b/plugins/antianqi/codex-harness-patterns/skills/completion-audit/SKILL.md @@ -0,0 +1,154 @@ +--- +name: completion-audit +description: "Before marking a non-trivial task complete (or telling the user it's done), treat completion as unproven and verify it against the actual current state. Derive requirements from the objective, identify the authoritative evidence for each, inspect that evidence, and only declare done when every requirement is satisfied. Use whenever the agent is about to say 'done' / 'complete' / 'ship it' / 'I finished' on a non-trivial task, especially when there is an active thread goal. Mirrors the completion audit section of the Codex goal continuation template (ext/goal/templates/goals/continuation.md)." +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/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/fork-context-decision/SKILL.md b/plugins/antianqi/codex-harness-patterns/skills/fork-context-decision/SKILL.md new file mode 100644 index 0000000..bc3c521 --- /dev/null +++ b/plugins/antianqi/codex-harness-patterns/skills/fork-context-decision/SKILL.md @@ -0,0 +1,140 @@ +--- +name: fork-context-decision +description: "When spawning a sub-agent, decide how much parent context to give it via the `fork_turns` parameter. Use every time you call `task` (or equivalent) to hand off work — the choice between `all` / `none` / a positive integer is one of the largest cost levers in multi-agent work. Mirrors the `fork_turns` semantics in codex-rs/ext/goal/src/multi_agents.rs (V2 multi-agent protocol)." +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/blob/main/codex-rs/core/src/session/multi_agents.rs +--- + +# Fork Context Decision + +`fork_turns` is the **single largest cost lever** in multi-agent work. `all` doubles your +context, `none` forces the sub-agent to re-derive from a brief, and the integer in between +is a precision knob. Pick wrong and you either pay main-model prices for routine work, or +you spawn a sub-agent that cannot do its job because it cannot see what came before. + +This Skill codifies the decision so you make it explicitly, not by accident. + +## 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 support a fork / context parameter (this Skill is then + irrelevant; skip). +- The sub-task is so trivial that the cost difference is noise (a one-line `grep`). +- You have already decided `fork_turns=0` or `none` (i.e. you have already chosen "no + context") and there is no decision to make. + +## The 3 fork modes + +| Mode | What the sub-agent sees | Cost | When to use | +|---|---|---|---| +| **`all`** (default if omitted) | All of parent's history | **High** — sub-agent pays for every turn you took | Sub-agent needs the *exact* reasoning that led to the current state. Rare. | +| **`N`** (positive integer) | Last N turns of parent | **Medium** — proportional to N | Sub-agent needs the *recent* context (the last few tool calls / decisions) but not the full history. Most common. | +| **`none`** (or `0`) | Nothing — pure brief | **Low** — only the brief | Sub-agent can do its job from a well-written brief alone. The default for `parallel-fanout`. | + +## Decision framework + +Before every `task` call, ask: + +1. **Can the sub-agent do the job from the brief alone?** + - **Yes** → `none` + - **No** → continue + +2. **Does it need recent decisions / tool outputs to do the job?** + - "Recent" = the last 3-10 turns + - **Yes** → `N` where N ≈ the number of turns that contain the needed context + - **No** → continue + +3. **Does it need the full reasoning that led here?** + - This is rare. Most sub-tasks do not. + - **Yes** → `all` (and consider whether the sub-agent should be a continuation of the + main agent instead of a fresh sub-agent) + +4. **Will the cost of `all` be acceptable?** + - If `all` would push the sub-agent over its own budget, choose `N` or `none`. + - If the sub-task is cheap and the cost of failure is high, `all` may be worth it. + +## Output contract + +Every time you call `task`, the user sees: + +- The chosen mode (`all` / `N` / `none`) in the brief or in a one-line preamble. +- A one-sentence reason: "fork=N because the brief alone misses the X decision made 3 + turns ago." + +## Process + +1. **State the chosen mode** in the brief, before writing the rest of it. This forces an + explicit decision. +2. **For `N`**, name the specific turns / events the sub-agent needs to see. Do not just + write `N=5`; write `N=5 because the last 5 turns contain the X decision`. +3. **For `none`**, the brief must be self-contained. If the brief references "the + conversation above" or "what we just decided," you have made a mistake — `none` requires + a complete brief. +4. **For `all`**, explicitly justify why the sub-agent needs the full history. Default + suspicion: you do not need `all`; you need `N`. + +## Example + +```text +> task(subagent=explore, run_in_background=true, + fork_turns=0, + prompt="") +launched explore (id: 5a3f); fork=none because the brief is self-contained +``` + +```text +> task(subagent=explore, run_in_background=true, + fork_turns=3, + prompt="The last 3 turns contain the design decision; resume from there. + ") +launched explore (id: 6b2c); fork=3 because the sub-task picks up after the auth redesign +decision +``` + +```text +> task(subagent=explore, run_in_background=true, + fork_turns=all, // rare + prompt="") +launched explore (id: 7c1a); fork=all because this sub-agent IS the continuation of the +debugging session +``` + +## Common pitfalls + +- **Do not default to `all`.** It is the most expensive answer. The Skill exists to move + work *off* `all`, not to confirm the obvious. +- **Do not default to `none` if the brief is incomplete.** An under-forked sub-agent will + fail silently. It is better to over-fork than under-fork on the first attempt; downgrade + on retry. +- **Do not pick `N` without naming the turns.** "N=5" is not a decision; "N=5 because the + last 5 turns contain the X decision" is. +- **Do not pick `all` "to be safe."** It is not safer; it is expensive. Safety comes from + a well-scoped brief + an explicit decision, not from dumping history. +- **Do not change the fork mode mid-stream.** If you started with `none` and the + sub-agent comes back saying "I need more context," do not re-spawn with `all`; instead, + send a follow-up `send_message` (or equivalent) with the specific context it needs. + Re-spawning wastes the work it already did. + +## Verification checklist + +- [ ] Did you choose `all` / `N` / `none` explicitly, not by leaving the default? +- [ ] Is the chosen mode justified in the brief or in a one-line preamble? +- [ ] For `N`: did you name the specific turns the sub-agent needs? +- [ ] For `none`: is the brief self-contained (no references to "above" or "earlier")? +- [ ] For `all`: did you explicitly justify why the full history is needed? +- [ ] Is the cost of the chosen mode acceptable (no surprise over-budget)? +- [ ] If a sub-agent came back saying "I need more context," did you send a follow-up + message rather than re-spawning with `all`? diff --git a/plugins/antianqi/codex-harness-patterns/skills/goal-persistence/SKILL.md b/plugins/antianqi/codex-harness-patterns/skills/goal-persistence/SKILL.md index 7ba36fd..b3e598d 100644 --- a/plugins/antianqi/codex-harness-patterns/skills/goal-persistence/SKILL.md +++ b/plugins/antianqi/codex-harness-patterns/skills/goal-persistence/SKILL.md @@ -1,12 +1,13 @@ --- name: goal-persistence -description: "Maintain an explicit north-star goal for the whole thread that survives compactions and detects drift. Use at the start of any non-trivial task (one-time set), after every user redirection (one-time update), and at every `context-pressure-compact` boundary (one-line alignment check). Mirrors codex-rs `Op::SetThreadMemoryMode` + `EventMsg::ThreadGoalUpdated` in protocol/src/protocol.rs." +description: "Maintain an explicit north-star goal for the whole thread that survives compactions and detects drift. Use at the start of any non-trivial task (one-time set), after every user redirection (one-time update), and at every `context-pressure-compact` boundary (one-line alignment check). Before declaring done, run a completion audit (see also `completion-audit` Skill). Mirrors codex-rs `Op::SetThreadMemoryMode` + `EventMsg::ThreadGoalUpdated` in protocol/src/protocol.rs and the continuation template in ext/goal/templates/goals/continuation.md." 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/blob/main/codex-rs/protocol/src/protocol.rs (SetThreadMemoryMode, ThreadGoalUpdatedEvent) + version: "1.0.0" + 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 @@ -19,6 +20,10 @@ 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: @@ -29,6 +34,8 @@ Activate when **any** of these is true: - 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 @@ -96,9 +103,63 @@ Activate when **any** of these is true: 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. **When the user finally says "done" / "ship it" / "looks good"**, mark the goal as +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. + 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 @@ -108,8 +169,11 @@ The user sees, in this order: - 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 +## Example goal file ```markdown # Goal — Auth refactor (OIDC alongside SAML) @@ -176,14 +240,18 @@ is "add OIDC without breaking SAML". 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 let the goal silently drift via tool calls.** The whole point of this - Skill is that drift is *visible*, not hidden. +- **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 6 sections (Set / Owner / Last checked / Version / Original +- [ ] 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 @@ -191,4 +259,8 @@ is "add OIDC without breaking SAML". - [ ] 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? -- [ ] On "done", did you mark the goal as achieved in the file (audit trail)? +- [ ] 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/parallel-fanout/SKILL.md b/plugins/antianqi/codex-harness-patterns/skills/parallel-fanout/SKILL.md index 982c787..6dff2f6 100644 --- a/plugins/antianqi/codex-harness-patterns/skills/parallel-fanout/SKILL.md +++ b/plugins/antianqi/codex-harness-patterns/skills/parallel-fanout/SKILL.md @@ -1,19 +1,25 @@ --- name: parallel-fanout -description: "Decompose a clearly independent task into 2+ parallel sub-tasks and dispatch them with `task` in one round trip, then aggregate. Use when the user task can be split along a clean boundary (independent files, independent probes, independent analyses) and serial execution would take materially longer. Mirrors codex-rs FuturesUnordered fan-out in thread_manager.rs." +description: "Decompose a clearly independent task into 2+ parallel sub-tasks and dispatch them with `task` in one round trip, then aggregate. Use when the user task can be split along a clean boundary (independent files, independent probes, independent analyses) and serial execution would take materially longer. Mirrors the `spawn_agent` tool in codex-rs's V2 multi-agent protocol, where the spawn is **explicit and opt-in** (not auto)." 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/blob/main/codex-rs/core/src/thread_manager.rs + version: "1.0.0" + inspired-by: https://github.com/openai/codex/blob/main/codex-rs/core/src/session/multi_agents.rs and core/src/thread_manager.rs + changes-from-v0.1.0: "Added explicit-spawn principle (P-20: spawn is opt-in, not auto); added `max_concurrency` awareness; cross-referenced `fork-context-decision` for per-sub-task cost control; cross-referenced `delegate-with-context` for the brief." --- # Parallel Fan-Out When a task splits cleanly into 2+ independent sub-tasks, dispatch them in parallel and -aggregate. Skip when the sub-tasks have data dependencies or when serial execution is fast -enough that the overhead of fan-out is not worth it. +aggregate. Skip when the sub-tasks have data dependencies, when serial execution is fast +enough that the overhead of fan-out is not worth it, or when the user has not opted in to +multi-agent work. + +**v1.0 update**: makes explicit that fan-out is an *opt-in decision* (mirroring Codex's +`MultiAgentMode::ExplicitRequestOnly` default), and ties fan-out to the cost-control +machinery in `fork-context-decision`. ## When to use @@ -24,8 +30,26 @@ Activate when **all** of the following hold: - Each sub-task is **bounded**: you can name what "done" looks like in one sentence. - The estimated wall-clock time of serial execution is **at least 2×** the longest single sub-task. If one sub-task dwarfs the others, fan-out buys you little. +- The user has **opted in** to multi-agent work for this task. See "Opt-in principle" below. - The user has not said "do it one by one" / "step by step" / "sequentially please". +### Opt-in principle (from Codex MultiAgentMode) + +Codex ships in `MultiAgentMode::ExplicitRequestOnly`: the agent does not spawn sub-agents +on its own — only when the user explicitly asks or the system instruction is set. We follow +the same default: + +- **Default**: do not fan out. Do the work yourself, or in sequence. +- **Opt-in triggers** (any one is enough): + - The user says "并行" / "fan out" / "do these in parallel" / "spawn agents". + - The user has set an AGENTS.md / system instruction that says "use sub-agents for this + class of task." + - The task is so large that serial execution would obviously exceed the user's patience + (judgment call — be conservative). + +When in doubt, ask the user before fan-out. The cost of a wrong fan-out is wasted sub-agent +spend; the cost of a wrong serial is just a few extra turns. + ## When NOT to use - The sub-tasks share state (e.g. all read/modify the same file in conflicting ways). @@ -35,6 +59,8 @@ Activate when **all** of the following hold: - The user explicitly asked for serial work or a careful step-by-step walkthrough. - You cannot articulate the boundary of each sub-task in one sentence. If you can't, you can't safely parallelise it. +- The user has not opted in to multi-agent work and the task is small enough to do + directly. ## Process @@ -50,38 +76,53 @@ Activate when **all** of the following hold: **Aggregation**: **Stop conditions**: + **Concurrency cap**: ``` -2. **Dispatch in parallel** with `task`. Use `run_in_background: true` for each so they overlap - in the same round trip. Pass a minimal-context brief to each — the original user request - plus the specific sub-task boundary, NOT the full history. +2. **For each sub-task, decide `fork_turns`** (see `fork-context-decision` Skill): + - Self-contained sub-task (look up, reformat, list) → `none` + - Sub-task depends on recent parent decisions → `N` (small) + - Sub-task is a continuation of the same debugging session → `all` (rare) + +3. **Dispatch in parallel** with `task`. Use `run_in_background: true` for each so they overlap + in the same round trip. Pass a **minimal-context brief** to each (see + `delegate-with-context` Skill) — not the full history. -3. **While waiting**, the orchestrating agent may draft the aggregation template (so the final +4. **Respect the concurrency cap.** If the user task has 8 sub-tasks and `max_concurrency` + is 5, dispatch 5, wait for one to finish, then dispatch the next. Do not fan out + unboundedly — the harness and the user's patience both have limits. + +5. **While waiting**, the orchestrating agent may draft the aggregation template (so the final merge is a fill-in, not a re-derivation). -4. **On all sub-tasks completing**: +6. **On all sub-tasks completing**: - Verify each met its pass condition. - If a sub-task drifted outside its boundary, **reject** and re-dispatch with a tighter brief. Do not absorb the drift. - If two sub-tasks produced conflicting facts (different numbers, different recommendations), surface the conflict to the user **before** aggregating. Do not silently pick one. -5. **Aggregate** into the agreed shape. Cite the source sub-task for each section so the user +7. **Aggregate** into the agreed shape. Cite the source sub-task for each section so the user can drill in. -6. **Report** the wall-clock time saved if you have it (use timestamps from the sub-task +8. **Report** the wall-clock time saved if you have it (use timestamps from the sub-task responses). This is how you earn the right to fan out again. +9. **Before declaring the whole task done**, run a `completion-audit` (separate Skill) on + the aggregated result. Fan-out is exactly the kind of work that produces confident-looking + but unverified deliverables. + ## Output contract The user sees, in this order: -- The Fan-out plan block (before any tool call). -- The list of dispatched sub-tasks (one line per `task` call). +- The Fan-out plan block (before any tool call), including the concurrency cap. +- The list of dispatched sub-tasks (one line per `task` call) with the chosen `fork_turns`. - The pass/fail per sub-task. - Any conflicts surfaced before aggregation. - The aggregated result. - (Optional) The wall-clock saving vs serial. +- A `completion-audit` on the final aggregation before declaring done. ## Example @@ -90,29 +131,35 @@ The user sees, in this order: **Sub-task 1**: Audit dependencies in /repo/server/Cargo.toml for known CVEs. Pass condition: a table of {crate, version, advisory_id, severity}. + fork_turns: none (look-up only). **Sub-task 2**: Audit dependencies in /repo/web/package.json for known CVEs. Pass condition: a table of {package, version, advisory_id, severity}. + fork_turns: none. **Sub-task 3**: List license of every direct dependency in /repo/server and /repo/web. Pass condition: a single table of {crate_or_package, license, copyleft_flag}. + fork_turns: none. **Aggregation**: Combine into a single SECURITY-REPORT.md at the repo root. **Stop conditions**: If sub-task 1 or 2 finds a critical CVE, surface immediately and do not wait for sub-task 3. +**Concurrency cap**: 3 (we have 3 sub-tasks, all run in parallel). ``` Then: ```text -> task(subagent=explore, run_in_background=true, +> task(subagent=explore, run_in_background=true, fork_turns=0, prompt="Audit /repo/server/Cargo.toml direct dependencies ...") -> task(subagent=explore, run_in_background=true, +> task(subagent=explore, run_in_background=true, fork_turns=0, prompt="Audit /repo/web/package.json direct dependencies ...") -> task(subagent=explore, run_in_background=true, +> task(subagent=explore, run_in_background=true, fork_turns=0, prompt="List licenses of /repo/server and /repo/web direct deps ...") ``` ## Common pitfalls +- **Do not fan out by default.** Codex ships in `MultiAgentMode::ExplicitRequestOnly` — + spawn is opt-in, not auto. Follow the same default. - **Do not fan out work that is too small.** A 200-line refactor is one task, not three. - **Do not fan out work with hidden dependencies.** If sub-task 2 might need to read what sub-task 1 wrote, that's serial. Don't pretend. @@ -123,13 +170,25 @@ Then: - **Do not aggregate silently.** If two sub-tasks disagree, the user must see the conflict. - **Do not fan out > 5 sub-tasks.** Beyond 5, the aggregation step becomes a bottleneck and context cost grows. For larger splits, ask the user first. +- **Do not skip the completion audit on the aggregation.** Fan-out produces more text + to be wrong about, not less. A confident-looking aggregated report is still unverified + until the audit is run. +- **Do not pick `all` for fan-out sub-tasks.** Fan-out sub-tasks should default to + `none` (look-up only) or small `N` (recent context). See `fork-context-decision`. +- **Do not exceed the concurrency cap.** If `max_concurrency=5` and you have 8 sub-tasks, + batch them, not all at once. ## Verification checklist -- [ ] Did you state the Fan-out plan block before any tool call? +- [ ] Did the user opt in (or is the task so large that opt-in is the only reasonable read)? +- [ ] Did you state the Fan-out plan block before any tool call, including the + concurrency cap? - [ ] Is each sub-task truly independent (no shared state, no data flow between them)? -- [ ] Did you pass a minimal-context brief, not the full history? +- [ ] Did you pass a minimal-context brief, not the full history? (`delegate-with-context`) +- [ ] Did you choose `fork_turns` for each sub-task explicitly? (`fork-context-decision`) - [ ] Did you use `run_in_background: true` so they overlap? +- [ ] Did you respect the concurrency cap (no unbounded fan-out)? - [ ] Did you surface conflicts before aggregating? - [ ] Did you cite the source sub-task for each section of the aggregation? +- [ ] Did you run a `completion-audit` on the aggregation before declaring done? - [ ] Did the wall-clock savings actually justify the fan-out? From 21afb474a0e3296dc1c328499689490074143ca6 Mon Sep 17 00:00:00 2001 From: antianqi Date: Mon, 24 Aug 2026 01:02:15 +0800 Subject: [PATCH 07/49] =?UTF-8?q?v0.5.0:=2014=20Skills=20=E2=80=94=20add?= =?UTF-8?q?=20subagent-family-tracking=20+=20goal-token-budgeting;=20upgra?= =?UTF-8?q?de=20context-pressure-compact=20+=20delegate-with-context=20to?= =?UTF-8?q?=20v1.0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit New Skills (2): - subagent-family-tracking P-23 agent-graph-store + SessionSource::SubAgent (parent/child tree, Open/Closed status, lost-child prevention) - goal-token-budgeting P-22 ext/goal/src/accounting.rs + continuation template (track token_budget, surface at 50/80/100%, stop at 100%) Skill upgrades to v1.0 (2): - context-pressure-compact + 64K retention budget (RETAINED_MESSAGE_TOKEN_BUDGET from P-10) + discarded count reporting + cross-references to all 5 persistent-state files - delegate-with-context + V2 message envelope (Message Type / Task name / Sender / Payload) + explicit return-path section + cross-references to fork-context-decision / model-router / subagent-family-tracking Total Skills: 14. Manifest bumped to 0.5.0. Validation: npm run check still passes for this plugin (OK plugin antianqi/codex-harness-patterns). --- .../antianqi/codex-harness-patterns/README.md | 85 ++++++---- .../codex-harness-patterns/plugin.json | 8 +- .../skills/context-pressure-compact/SKILL.md | 82 +++++++--- .../skills/delegate-with-context/SKILL.md | 126 ++++++++++++--- .../skills/goal-token-budgeting/SKILL.md | 152 +++++++++++++++++ .../skills/subagent-family-tracking/SKILL.md | 153 ++++++++++++++++++ 6 files changed, 527 insertions(+), 79 deletions(-) create mode 100644 plugins/antianqi/codex-harness-patterns/skills/goal-token-budgeting/SKILL.md create mode 100644 plugins/antianqi/codex-harness-patterns/skills/subagent-family-tracking/SKILL.md diff --git a/plugins/antianqi/codex-harness-patterns/README.md b/plugins/antianqi/codex-harness-patterns/README.md index 5e61b88..0c77136 100644 --- a/plugins/antianqi/codex-harness-patterns/README.md +++ b/plugins/antianqi/codex-harness-patterns/README.md @@ -4,7 +4,8 @@ A focused collection of Skills distilled from the **OpenAI Codex harness v0.149. 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, or paying main-model prices for cheap-model work. +original goal, paying main-model prices for cheap-model work, or losing track of which +sub-agent is doing what. ## The problem @@ -32,6 +33,10 @@ Long agentic sessions fail for predictable reasons: that a cheap model could handle in a fraction of the time and cost. - **Sub-agent context over-spend** — the agent gives every sub-agent the full history when a small brief would do. +- **Lost sub-agents** — the agent spawns 3 children, loses track of which is which, and either + duplicates work or never reads a child's result. +- **Runaway goal cost** — the user sets a token budget for a goal; the agent blows past it + without surfacing the warning. OpenAI's Codex harness solves each of these with specific code (see [`codex-rs/core/src/compact.rs`](https://github.com/openai/codex/blob/main/codex-rs/core/src/compact.rs), @@ -39,7 +44,9 @@ OpenAI's Codex harness solves each of these with specific code (see [`session/turn.rs::run_turn`](https://github.com/openai/codex/blob/main/codex-rs/core/src/session/turn.rs), [`context/world_state.rs`](https://github.com/openai/codex/blob/main/codex-rs/core/src/context/world_state.rs), [`ext/goal/templates/goals/continuation.md`](https://github.com/openai/codex/blob/main/codex-rs/ext/goal/templates/goals/continuation.md), -[`model-provider-info/`](https://github.com/openai/codex/tree/main/codex-rs/model-provider-info)) +[`ext/goal/src/accounting.rs`](https://github.com/openai/codex/blob/main/codex-rs/ext/goal/src/accounting.rs), +[`model-provider-info/`](https://github.com/openai/codex/tree/main/codex-rs/model-provider-info), +[`agent-graph-store/`](https://github.com/openai/codex/tree/main/codex-rs/agent-graph-store)) and reports a 3× score lift on ARC-AGI-3 with the same model, just by changing the harness. This Plugin packages those patterns as portable Skills. @@ -70,50 +77,73 @@ each non-trivial change." "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%." ``` **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.4.0, 12 Skills) +## What this Plugin adds (v0.5.0, 14 Skills) -Twelve Skills, all Skill-only (no MCP server, no network access): +Fourteen Skills, all Skill-only (no MCP server, no network access): -| # | Skill | When to activate | v0.4.0 | +| # | 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 | -| 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 | -| 3 | `parallel-fanout` | The user task is clearly decomposable into 2+ independent sub-tasks (independent files, independent probes, independent analyses). | v0.1.0 → **v1.0** | +| 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** | +| 3 | `parallel-fanout` | The user task is clearly decomposable into 2+ independent sub-tasks (independent files, independent probes, independent analyses). | v0.1.0 → v1.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 | | 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 | -| 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 | +| 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.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 | | 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 | -| 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** | +| 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 | | 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 | -| 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 (new)** | -| 12 | `fork-context-decision` | About to call `task` to hand off a sub-task. Decides how much parent context to give the sub-agent via the `fork_turns` parameter. | **v0.4.0 (new)** | +| 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 | +| 12 | `fork-context-decision` | About to call `task` to hand off a sub-task. Decides how much parent context to give the sub-agent via the `fork_turns` parameter. | v0.4.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 (new)** | +| 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 (new)** | + +## v0.5.0 changelog + +### Added + +- `subagent-family-tracking` Skill — track the parent/child thread tree of spawned sub-agents. + Mirrors `codex-rs/agent-graph-store/`'s `ThreadSpawnEdgeStatus` (Open/Closed) plus the + `SessionSource::SubAgent(SubAgentSource::ThreadSpawn)` marker. +- `goal-token-budgeting` Skill — when the user sets an explicit `token_budget` on a goal, + track running usage, surface at 50%/80%/100% thresholds, stop at 100% and ask. Mirrors + `ext/goal/src/accounting.rs` (GoalAccountingState) and the "Tokens used / Token budget / + Tokens remaining" section of the goal continuation template. + +### Updated + +- `context-pressure-compact` v1.0 — added the 64K retention budget concept from + `compact_remote_v2.rs::RETAINED_MESSAGE_TOKEN_BUDGET`. Snapshots now report the retained + token estimate and the "discarded N tool calls / M lines" count. Cross-referenced all + five persistent-state files so the snapshot is the single coordination point. +- `delegate-with-context` v1.0 — added the V2 message envelope (Message Type / Task name / + Sender / Payload) so sub-agent replies are parsed consistently. Added explicit + "return path" section in the brief. Cross-referenced `fork-context-decision`, + `model-router`, and `subagent-family-tracking`. + +Total Skills: 14 (12 from v0.4.0 + 2 new + 2 skill upgrades to v1.0). -## v0.4.0 changelog +## v0.4.0 changelog (prior) ### Added -- `completion-audit` Skill — derive requirements, identify authoritative evidence, verify each, - only declare done when every requirement has its own ✅. Mirrors the completion-audit section - of the Codex goal continuation template. -- `fork-context-decision` Skill — pick `all` / `N` / `none` for `fork_turns` explicitly, not - by default. Mirrors the `fork_turns` semantics in Codex's V2 multi-agent protocol. +- `completion-audit` Skill. +- `fork-context-decision` Skill. ### Updated -- `goal-persistence` v1.0 — incorporated the 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. -- `parallel-fanout` v1.0 — added explicit-spawn principle (P-20: spawn is opt-in, not auto). - Added `max_concurrency` awareness. Cross-referenced `fork-context-decision` and - `delegate-with-context`. Added `completion-audit` on the aggregation before declaring - done. +- `goal-persistence` v1.0. +- `parallel-fanout` v1.0. ## Requirements @@ -129,13 +159,12 @@ Twelve Skills, all Skill-only (no MCP server, no network access): tool calls). - **No file modification outside the agent's existing write surface.** The Skills may instruct the agent to use `write` / `edit` / `bash` to persist a compact summary, a plan file, a - world-state file, or a goal file, but only on paths the user already authorised through the - active session. + world-state file, a goal file, a family file, or a usage log, but only on paths the user + already authorised through the active session. - **No sub-agent launch without user intent.** `parallel-fanout`, `delegate-with-context`, and `fork-context-decision` instruct the agent to use `task` for fan-out / delegation, but only when the user task is independently decomposable **and** the user has opted in to - multi-agent work. The agent must still justify the decomposition in the plan and stop if - the user says "do it one by one". + multi-agent work. - **No model switching that the harness does not support.** `model-router` only works if the underlying `task` tool exposes `model_config_id` (or equivalent). If the harness does not support model routing, the Skill degrades to "classify the sub-task" and the model choice diff --git a/plugins/antianqi/codex-harness-patterns/plugin.json b/plugins/antianqi/codex-harness-patterns/plugin.json index 424dea3..c2f7fbd 100644 --- a/plugins/antianqi/codex-harness-patterns/plugin.json +++ b/plugins/antianqi/codex-harness-patterns/plugin.json @@ -1,8 +1,8 @@ { "$schema": "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json", "name": "codex-harness-patterns", - "version": "0.4.0", - "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, and fork-context decision. 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, or prove that a non-trivial task is actually done. Inspired by codex-rs/core/src/compact.rs, run_turn, Op/EventMsg protocol, InterAgentCommunication, WorldState, ext/goal continuation template, model-provider-info, and the models-manager sub-systems.", + "version": "0.5.0", + "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, and goal token budgeting. 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, or stay within an explicit goal token budget. Inspired by codex-rs/core/src/compact.rs, run_turn, Op/EventMsg protocol, InterAgentCommunication, WorldState, ext/goal continuation template + accounting, model-provider-info, models-manager, agent-graph-store, and the V2 multi-agent protocol.", "author": { "name": "antianqi", "url": "https://github.com/antianqi" @@ -25,6 +25,8 @@ "goal", "model-routing", "completion-audit", - "fork-context" + "fork-context", + "family-tracking", + "token-accounting" ] } 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 index 583c1e7..04cfb6c 100644 --- a/plugins/antianqi/codex-harness-patterns/skills/context-pressure-compact/SKILL.md +++ b/plugins/antianqi/codex-harness-patterns/skills/context-pressure-compact/SKILL.md @@ -1,19 +1,24 @@ --- name: context-pressure-compact -description: "Compress a long-running multi-step task into a structured state summary before continuing, so the agent can keep going without losing track of the original goal. Use when the active `todowrite` exceeds 5 items, after ~20 tool calls, when the user says 'compact' / 'summarize so far' / 'we need to refocus', or when context usage is visibly heavy. Mirrors codex-rs/core/src/compact.rs::run_pre_sampling_compact." +description: "Compress a long-running multi-step task into a structured state summary before continuing, so the agent can keep going without losing track of the original goal. Use when the active `todowrite` exceeds 5 items, after ~20 tool calls, when the user says 'compact' / 'summarize so far' / 'we need to refocus', or when context usage is visibly heavy. Mirrors codex-rs/core/src/compact.rs::run_pre_sampling_compact and the v2 64K retention budget (RETAINED_MESSAGE_TOKEN_BUDGET)." 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/blob/main/codex-rs/core/src/compact.rs + version: "1.0.0" + 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. +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 @@ -26,6 +31,8 @@ Activate when **any** of these is true: - 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. @@ -39,7 +46,13 @@ its own cost; the savings only matter once the conversation is genuinely heavy. ## Process 1. **Freeze new work.** Do not start any new tool call before the snapshot is written. -2. **Write the snapshot** to a single fenced block, in this exact shape: +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 — @@ -63,14 +76,25 @@ its own cost; the savings only matter once the conversation is genuinely heavy. - **Next concrete step**: + + **Retained token estimate**: (target: ~64K, see RETAINED_MESSAGE_TOKEN_BUDGET) + **Discarded this turn**: ``` -3. **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. -4. **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. -5. **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. +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 @@ -79,6 +103,7 @@ 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 @@ -87,50 +112,61 @@ Every time you apply this Skill, the user sees: **Goal**: Refactor the auth subsystem to support OIDC without breaking the existing SAML path. -**Done**: +**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**: +**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**: +**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**: +**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**: +**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 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 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 index fd39708..3a7b522 100644 --- a/plugins/antianqi/codex-harness-patterns/skills/delegate-with-context/SKILL.md +++ b/plugins/antianqi/codex-harness-patterns/skills/delegate-with-context/SKILL.md @@ -1,22 +1,27 @@ --- name: delegate-with-context -description: "When delegating a sub-task to another agent (via `task`), prepare a minimal-context brief instead of dumping the full conversation history. Use when handing off a sub-task boundary, when a sub-agent needs the user's goal + the specific boundary + the pass condition + the minimal inputs, and when the conversation history is large enough that forwarding it all would waste tokens. Mirrors codex-rs InterAgentCommunication in Op / CollabAgentSpawnBegin." +description: "When delegating a sub-task to another agent (via `task`), prepare a minimal-context brief instead of dumping the full conversation history. Use when handing off a sub-task boundary, when a sub-agent needs the user's goal + the specific boundary + the pass condition + the minimal inputs, and when the conversation history is large enough that forwarding it all would waste tokens. Mirrors codex-rs InterAgentCommunication in Op / CollabAgentSpawnBegin, plus the explicit 'Message Type / Task name / Sender / Payload' message envelope from V2 multi-agent protocol." 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/blob/main/codex-rs/protocol/src/protocol.rs (Op::InterAgentCommunication, CollabAgentSpawnBegin/End) + version: "1.0.0" + inspired-by: https://github.com/openai/codex/blob/main/codex-rs/protocol/src/protocol.rs (Op::InterAgentCommunication, CollabAgentSpawnBegin) and core/src/session/multi_agents.rs + changes-from-v0.2.0: "Added the message envelope format (Message Type / Task name / Sender / Payload) from P-20 V2; added explicit 'this is the sub-agent return path' section; cross-referenced fork-context-decision for fork_turns choice." --- # Delegate With Context -When you spawn a sub-agent (`task` or equivalent), the brief you pass is the only thing it sees. -A bad brief makes the sub-agent re-derive the whole conversation; a good brief gives it exactly -what it needs to do its job and nothing more. +When you spawn a sub-agent (`task` or equivalent), the brief you pass is the only thing +it sees. A bad brief makes the sub-agent re-derive the whole conversation; a good brief +gives it exactly what it needs to do its job and nothing more. -This Skill is the inverse of "pass the full history" — the full history is the most expensive -context you can give a sub-agent, and it is rarely what the sub-agent needs. +This Skill is the inverse of "pass the full history" — the full history is the most +expensive context you can give a sub-agent, and it is rarely what the sub-agent needs. + +**v1.0 update**: now includes the message envelope format used by Codex's V2 multi-agent +protocol, and a clear "this is the return path" section so the sub-agent knows how to +deliver its result. ## When to use @@ -69,24 +74,68 @@ Activate when **any** of these is true: - Do not change the public API. - Do not introduce new dependencies. - + + **Return path** (how the sub-agent reports back; see v1 message envelope below): + - Reply on the analysis channel with this exact envelope: + ``` + Message Type: FINAL_ANSWER + Task name: + Sender: + Payload: + + ``` + - Keep the payload under ~10 lines unless the task is "produce a long report." + + **Model tier** (cheap / medium / main; see `model-router` Skill): + - ``` -2. **Call `task` with the brief as the prompt.** The full conversation history is *not* in - the prompt; the brief is. -3. **Verify the brief round-tripped.** Read the sub-agent's first response. If it is solving +2. **Choose `fork_turns`** explicitly (see `fork-context-decision` Skill): + - Self-contained sub-task → `none` + - Needs recent context → small `N` + - Continuation of same debugging session → `all` (rare) + +3. **Call `task`** with the brief as the prompt. The full conversation history is *not* + in the prompt; the brief is. + +4. **Verify the brief round-tripped.** Read the sub-agent's first response. If it is solving the wrong problem, your brief failed — do not let it finish. Stop and re-brief. -4. **If the sub-agent needs more context mid-task**, send a follow-up brief in the same + +5. **Receive the result** in the message envelope format. The sub-agent's reply should + match the envelope; if it doesn't, treat the reply as unverified raw output and re-parse. + +6. **If the sub-agent needs more context mid-task**, send a follow-up brief in the same shape, not the original full history. -5. **On return, validate against the pass condition.** If unmet, re-dispatch with a tighter + +7. **On return, validate against the pass condition.** If unmet, re-dispatch with a tighter brief; do not patch the result yourself unless the fix is trivial. +8. **Record in the family file** (see `subagent-family-tracking` Skill) so the tree stays + up to date. + +## Message envelope (V2 protocol) + +Codex's V2 multi-agent protocol uses a structured envelope for sub-agent replies: + +```text +Message Type: +Task name: +Sender: +Payload: + +``` + +When your sub-agent replies, **expect this envelope** and parse it accordingly. If the +reply is plain prose with no envelope, treat it as `MESSAGE` (an interim update, not the +final answer) and either wait for the `FINAL_ANSWER` or re-brief to clarify. + ## Output contract The user sees, in this order: -- The Sub-task brief block (before the `task` call). -- The `task` invocation (one line). -- The sub-agent's first sentence (or its pass/fail against the pass condition). +- The Sub-task brief block (before the `task` call), including the chosen `fork_turns`. +- The `task` invocation (one line, with the `task_name`). +- The sub-agent's reply, parsed (envelope + payload). - (If failed) the re-brief, not a silent retry. ## Example @@ -115,27 +164,50 @@ The user sees, in this order: **Constraints**: - No new dependencies (no `rust_decimal`, `num-format`, etc.) - Match the existing function signature style in `money.rs` + +**Return path**: reply on the analysis channel with `Message Type: FINAL_ANSWER, Sender: +, Payload: ` + +**Model tier**: cheap — single-function reformat, no judgement needed ``` Then: ```text -> task(subagent=explore, +> task(subagent=explore, run_in_background=true, fork_turns=0, prompt="") +launched explore (id: 5a3f); fork=none, tier=cheap +``` + +Sub-agent reply (parsed): + +```text +Message Type: FINAL_ANSWER +Task name: money-format +Sender: explore-5a3f +Payload: +Added `format_currency` to src/money.rs:42-58, plus 4 unit tests at lines 78-110. All +pass. Diff at /tmp/money-diff.patch. ``` ## Common pitfalls -- **Do not pass the full conversation history as context.** That is the failure mode this Skill - exists to prevent. Pass the brief. +- **Do not pass the full conversation history as context.** That is the failure mode this + Skill exists to prevent. Pass the brief. - **Do not write a brief that says "see above".** The sub-agent does not have "above". -- **Do not omit the pass condition.** Without it, the sub-agent picks its own definition of - done, which is rarely yours. -- **Do not omit the constraints.** "Don't refactor adjacent code" saves a 3-message ping-pong. -- **Do not over-brief.** A 200-line brief for a one-function change is itself a token waste. +- **Do not omit the pass condition.** Without it, the sub-agent picks its own definition + of done, which is rarely yours. +- **Do not omit the constraints.** "Don't refactor adjacent code" saves a 3-message + ping-pong. +- **Do not over-brief.** A 200-line brief for a one-function change is itself a token + waste. - **Do not under-brief.** "Look at the auth code" is a wish, not a brief. -- **Do not brief a sub-task boundary that is fuzzy.** Decompose first (`plan-stream-emit`), - then brief the resulting steps. +- **Do not brief a sub-task boundary that is fuzzy.** Decompose first + (`plan-stream-emit`), then brief the resulting steps. +- **Do not expect prose replies.** If the reply is not in the envelope format, treat it + as unverified and re-brief the sub-agent to use the envelope. +- **Do not forget to record in the family file.** If you do not, the next turn does not + know which sub-agent did what. ## Verification checklist @@ -146,4 +218,8 @@ Then: - [ ] Is the pass condition one checkable sentence? - [ ] Is the output shape specific (patch / report / JSON)? - [ ] Did you list the constraints to head off re-work? +- [ ] Did you specify the return-path envelope? +- [ ] Did you choose `fork_turns` explicitly (`fork-context-decision`)? +- [ ] Did you choose the model tier (`model-router`)? - [ ] Did the sub-agent's first response show it understood the brief? +- [ ] Did you record the spawn in the family file (`subagent-family-tracking`)? 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..829a356 --- /dev/null +++ b/plugins/antianqi/codex-harness-patterns/skills/goal-token-budgeting/SKILL.md @@ -0,0 +1,152 @@ +--- +name: goal-token-budgeting +description: "When the user sets an explicit token budget on a goal, track the running usage and report it on completion. Use whenever `goal-persistence` is active and the user provided a `token_budget` (either initially or via `Op::SetThreadMemoryMode`). Mirrors codex-rs `ext/goal/src/accounting.rs` (GoalAccountingState) and the 'Tokens used / Token budget / Tokens remaining' section of the goal continuation template." +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/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/subagent-family-tracking/SKILL.md b/plugins/antianqi/codex-harness-patterns/skills/subagent-family-tracking/SKILL.md new file mode 100644 index 0000000..70fb1cd --- /dev/null +++ b/plugins/antianqi/codex-harness-patterns/skills/subagent-family-tracking/SKILL.md @@ -0,0 +1,153 @@ +--- +name: subagent-family-tracking +description: "Track the parent/child thread tree of sub-agents you have spawned, so you know who is alive, who has finished, and which siblings share context. Use every time you spawn a sub-agent and the task may fan out (multiple children) or chain (a child spawning its own children). Mirrors codex-rs `agent-graph-store` (parent→child edges with Open/Closed status) plus the `SessionSource::SubAgent(SubAgentSource::ThreadSpawn)` marker." +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/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? From 3cfbb2a4612e0120618e02fb0dda3d9b2e8b9163 Mon Sep 17 00:00:00 2001 From: antianqi Date: Mon, 24 Aug 2026 11:24:38 +0800 Subject: [PATCH 08/49] =?UTF-8?q?v0.6.0:=2018=20Skills=20total=20=E2=80=94?= =?UTF-8?q?=20add=20error-recovery-strategy,=20retry-with-backoff,=20strea?= =?UTF-8?q?ming-output-reader,=20session-handoff?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Four new Skills extracted from the 'error / streaming / session-end' theme: - error-recovery-strategy 4-bucket classification (transient / deterministic / stale / unknown) -> 5-action decision tree (retry / switch / fallback / refresh-then-retry / ask-user / skip); categorical, not reflexive - retry-with-backoff explicit retry policy (max 3, base 2s, max 30s, full jitter, 60s total budget); respects Retry-After; hard ceiling; always escalates - streaming-output-reader bounded-chunk reads (head / tail / grep) with cumulative summary; max 3 reads per stream; never loop, never buffer to context - session-handoff at session end, write a handoff file so the next session can pick up in 30 seconds; mirrors state/runtime/recovery.rs Total Skills: 18. Manifest bumped to 0.6.0. Validation: npm run check still passes for this plugin (OK plugin antianqi/codex-harness-patterns). --- .../antianqi/codex-harness-patterns/README.md | 77 +++---- .../codex-harness-patterns/plugin.json | 10 +- .../skills/error-recovery-strategy/SKILL.md | 149 +++++++++++++ .../skills/retry-with-backoff/SKILL.md | 155 +++++++++++++ .../skills/session-handoff/SKILL.md | 203 ++++++++++++++++++ .../skills/streaming-output-reader/SKILL.md | 151 +++++++++++++ 6 files changed, 706 insertions(+), 39 deletions(-) create mode 100644 plugins/antianqi/codex-harness-patterns/skills/error-recovery-strategy/SKILL.md create mode 100644 plugins/antianqi/codex-harness-patterns/skills/retry-with-backoff/SKILL.md create mode 100644 plugins/antianqi/codex-harness-patterns/skills/session-handoff/SKILL.md create mode 100644 plugins/antianqi/codex-harness-patterns/skills/streaming-output-reader/SKILL.md diff --git a/plugins/antianqi/codex-harness-patterns/README.md b/plugins/antianqi/codex-harness-patterns/README.md index 0c77136..32da00b 100644 --- a/plugins/antianqi/codex-harness-patterns/README.md +++ b/plugins/antianqi/codex-harness-patterns/README.md @@ -4,8 +4,9 @@ A focused collection of Skills distilled from the **OpenAI Codex harness v0.149. 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, or losing track of which -sub-agent is doing what. +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. ## The problem @@ -37,6 +38,12 @@ Long agentic sessions fail for predictable reasons: duplicates work or never reads a child's result. - **Runaway goal cost** — the user sets a token budget for a goal; the agent blows past it without surfacing the warning. +- **Silent retry** — the agent retries a `deterministic` error (permission denied, file not + found) three times in a row, burning the same error each time. +- **Streaming overflow** — the agent reads an unbounded stream in one go, filling the + context with raw bytes instead of a summary. +- **Session loss** — the user steps away; next session starts with no idea what was in + progress. OpenAI's Codex harness solves each of these with specific code (see [`codex-rs/core/src/compact.rs`](https://github.com/openai/codex/blob/main/codex-rs/core/src/compact.rs), @@ -46,7 +53,9 @@ OpenAI's Codex harness solves each of these with specific code (see [`ext/goal/templates/goals/continuation.md`](https://github.com/openai/codex/blob/main/codex-rs/ext/goal/templates/goals/continuation.md), [`ext/goal/src/accounting.rs`](https://github.com/openai/codex/blob/main/codex-rs/ext/goal/src/accounting.rs), [`model-provider-info/`](https://github.com/openai/codex/tree/main/codex-rs/model-provider-info), -[`agent-graph-store/`](https://github.com/openai/codex/tree/main/codex-rs/agent-graph-store)) +[`agent-graph-store/`](https://github.com/openai/codex/tree/main/codex-rs/agent-graph-store), +[`code-mode/src/grpc_session/reconnect.rs`](https://github.com/openai/codex/blob/main/codex-rs/code-mode/src/grpc_session/reconnect.rs), +[`state/src/runtime/recovery.rs`](https://github.com/openai/codex/blob/main/codex-rs/state/src/runtime/recovery.rs)) and reports a 3× score lift on ARC-AGI-3 with the same model, just by changing the harness. This Plugin packages those patterns as portable Skills. @@ -81,69 +90,65 @@ each non-trivial change." "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.5.0, 14 Skills) +## What this Plugin adds (v0.6.0, 18 Skills) -Fourteen Skills, all Skill-only (no MCP server, no network access): +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 | -| 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** | +| 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 | | 3 | `parallel-fanout` | The user task is clearly decomposable into 2+ independent sub-tasks (independent files, independent probes, independent analyses). | v0.1.0 → v1.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 | | 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 | -| 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.0** | +| 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.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 | | 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 | | 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 | | 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 | | 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 | | 12 | `fork-context-decision` | About to call `task` to hand off a sub-task. Decides how much parent context to give the sub-agent via the `fork_turns` parameter. | v0.4.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 (new)** | -| 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 (new)** | +| 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 | +| 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 | +| 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 (new)** | +| 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 (new)** | +| 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 (new)** | +| 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 (new)** | -## v0.5.0 changelog +## v0.6.0 changelog ### Added -- `subagent-family-tracking` Skill — track the parent/child thread tree of spawned sub-agents. - Mirrors `codex-rs/agent-graph-store/`'s `ThreadSpawnEdgeStatus` (Open/Closed) plus the - `SessionSource::SubAgent(SubAgentSource::ThreadSpawn)` marker. -- `goal-token-budgeting` Skill — when the user sets an explicit `token_budget` on a goal, - track running usage, surface at 50%/80%/100% thresholds, stop at 100% and ask. Mirrors - `ext/goal/src/accounting.rs` (GoalAccountingState) and the "Tokens used / Token budget / - Tokens remaining" section of the goal continuation template. - -### Updated - -- `context-pressure-compact` v1.0 — added the 64K retention budget concept from - `compact_remote_v2.rs::RETAINED_MESSAGE_TOKEN_BUDGET`. Snapshots now report the retained - token estimate and the "discarded N tool calls / M lines" count. Cross-referenced all - five persistent-state files so the snapshot is the single coordination point. -- `delegate-with-context` v1.0 — added the V2 message envelope (Message Type / Task name / - Sender / Payload) so sub-agent replies are parsed consistently. Added explicit - "return path" section in the brief. Cross-referenced `fork-context-decision`, - `model-router`, and `subagent-family-tracking`. +- `error-recovery-strategy` Skill — 4-bucket classification (transient / deterministic / stale / unknown) → 5-action decision tree (retry / switch / fallback / refresh-then-retry / ask-user / skip). Mirrors the `code-mode` reconnect philosophy and the `MultiAgentMode::ExplicitRequestOnly` opt-in principle. +- `retry-with-backoff` Skill — explicit retry policy: max 3 attempts, base 2s, max 30s, full jitter, 60s total budget. Respects `Retry-After`. Hard ceiling, no silent extension. Always escalates on exhaustion. +- `streaming-output-reader` Skill — read in bounded chunks (head / tail / grep), write a cumulative summary, stop after at most 3 reads. Mirrors the `WebsocketSession.last_request` incremental pattern and the `unified_exec` background-command pattern. +- `session-handoff` Skill — at session end, write a structured handoff file (verbatim goal, state file references, done/in-progress items, next concrete step, critical paths, "might be wrong" risks). Mirrors `state/runtime/recovery.rs` (DB-backed resume) and the `rollout_migration_state` migration. -Total Skills: 14 (12 from v0.4.0 + 2 new + 2 skill upgrades to v1.0). +Total Skills: 18 (14 from v0.5.0 + 4 new). -## v0.4.0 changelog (prior) +## v0.5.0 changelog (prior) ### Added -- `completion-audit` Skill. -- `fork-context-decision` Skill. +- `subagent-family-tracking` Skill. +- `goal-token-budgeting` Skill. ### Updated -- `goal-persistence` v1.0. -- `parallel-fanout` v1.0. +- `context-pressure-compact` v1.0. +- `delegate-with-context` v1.0. ## Requirements @@ -159,8 +164,8 @@ Total Skills: 14 (12 from v0.4.0 + 2 new + 2 skill upgrades to v1.0). tool calls). - **No file modification outside the agent's existing write surface.** The Skills may instruct the agent to use `write` / `edit` / `bash` to persist a compact summary, a plan file, a - world-state file, a goal file, a family file, or a usage log, but only on paths the user - already authorised through the active session. + world-state file, a goal file, a family file, a handoff file, or a usage log, but only on + paths the user already authorised through the active session. - **No sub-agent launch without user intent.** `parallel-fanout`, `delegate-with-context`, and `fork-context-decision` instruct the agent to use `task` for fan-out / delegation, but only when the user task is independently decomposable **and** the user has opted in to diff --git a/plugins/antianqi/codex-harness-patterns/plugin.json b/plugins/antianqi/codex-harness-patterns/plugin.json index c2f7fbd..e16e7ea 100644 --- a/plugins/antianqi/codex-harness-patterns/plugin.json +++ b/plugins/antianqi/codex-harness-patterns/plugin.json @@ -1,8 +1,8 @@ { "$schema": "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json", "name": "codex-harness-patterns", - "version": "0.5.0", - "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, and goal token budgeting. 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, or stay within an explicit goal token budget. Inspired by codex-rs/core/src/compact.rs, run_turn, Op/EventMsg protocol, InterAgentCommunication, WorldState, ext/goal continuation template + accounting, model-provider-info, models-manager, agent-graph-store, and the V2 multi-agent protocol.", + "version": "0.6.0", + "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, and session handoff. 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, or hand off a session cleanly.", "author": { "name": "antianqi", "url": "https://github.com/antianqi" @@ -27,6 +27,10 @@ "completion-audit", "fork-context", "family-tracking", - "token-accounting" + "token-accounting", + "error-recovery", + "retry", + "streaming", + "session-handoff" ] } 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..575b24b --- /dev/null +++ b/plugins/antianqi/codex-harness-patterns/skills/error-recovery-strategy/SKILL.md @@ -0,0 +1,149 @@ +--- +name: error-recovery-strategy +description: "When a tool call, sub-agent task, or external operation fails, decide between retry / switch / fallback / ask-user / skip. Use whenever a tool returns a non-success result, a sub-agent reports failure, or an exception escapes from a call. The decision must be explicit, not reflexive. Mirrors the resilience patterns in codex-rs `code-mode/src/grpc_session/reconnect.rs` (binding-replacement over re-retry) and the `MultiAgentMode::ExplicitRequestOnly` philosophy (don't auto-recover without user signal)." +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/blob/main/codex-rs/code-mode/src/grpc_session/reconnect.rs and core/src/session/multi_agents.rs +--- + +# 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(subagent=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/retry-with-backoff/SKILL.md b/plugins/antianqi/codex-harness-patterns/skills/retry-with-backoff/SKILL.md new file mode 100644 index 0000000..8896373 --- /dev/null +++ b/plugins/antianqi/codex-harness-patterns/skills/retry-with-backoff/SKILL.md @@ -0,0 +1,155 @@ +--- +name: retry-with-backoff +description: "When a `transient` error has been classified (see `error-recovery-strategy`), execute the retry with explicit backoff policy: max attempts, base delay, exponential growth, jitter, and a hard ceiling on total time. Use every time you decide to retry. Mirrors the policy choices implicit in codex-rs `code-mode/src/grpc_session/reconnect.rs::get_or_open_binding` (no delay between generations, but bounded by `Semaphore(1)` concurrency) and the W3C retry semantics." +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/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/session-handoff/SKILL.md b/plugins/antianqi/codex-harness-patterns/skills/session-handoff/SKILL.md new file mode 100644 index 0000000..d039821 --- /dev/null +++ b/plugins/antianqi/codex-harness-patterns/skills/session-handoff/SKILL.md @@ -0,0 +1,203 @@ +--- +name: session-handoff +description: "At the end of a session, write a state file that lets the next session pick up exactly where this one left off. Use when a session is ending (user says 'done for today' / context is about to compact / time is up) and there is non-trivial work in progress. Mirrors codex-rs `state/migrations/0047_rollout_migration_state.sql` (explicit migration state) and the `state/runtime/recovery.rs` pattern (DB-backed resume on crash)." +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/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/streaming-output-reader/SKILL.md b/plugins/antianqi/codex-harness-patterns/skills/streaming-output-reader/SKILL.md new file mode 100644 index 0000000..6fbbdf7 --- /dev/null +++ b/plugins/antianqi/codex-harness-patterns/skills/streaming-output-reader/SKILL.md @@ -0,0 +1,151 @@ +--- +name: streaming-output-reader +description: "Read long streaming responses (SSE / WebSocket chunks / `tail -f` / long-running commands) in a way that does not block, does not buffer the whole thing in context, and does not miss output. Use whenever a tool returns a stream that is too long for a single read, or whenever a single read would force the agent to wait instead of doing other work. Mirrors the `WebsocketSession.last_request` incremental pattern in codex-rs/core/src/client.rs and the `unified_exec` background-command pattern in codex-rs/core/src/unified_exec/." +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/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? From f6d18a9285631315fa72066ffc37e047aaa4d146 Mon Sep 17 00:00:00 2001 From: antianqi Date: Mon, 24 Aug 2026 11:49:16 +0800 Subject: [PATCH 09/49] v0.6.1: rewrite all 18 Skill frontmatter descriptions for LLM matching Each Skill's description: field now uses a structured 4-line format: description: | . USE WHEN: . TRIGGER PHRASES: . SKIP WHEN: . This makes the descriptions keyword-greppable (ECONNREFUSED, permission denied, etc.) so the LLM matches on real signals instead of interpreting abstract prose. All 18 trigger phrases now spelled out in English AND Chinese. The 'Can I remember to use these skills?' question from the user inspired this change: the previous abstract descriptions were too vague for reliable LLM matching. This patch makes every Skill's trigger conditions explicit and greppable. Versions: manifest 0.6.0 -> 0.6.1 (patch: frontmatter only); all Skill versions 0.1.0/0.2.0/.../1.0.0 -> +0.0.1. No behavioral changes to Skill process / output / examples / checklist. Only the frontmatter description field was rewritten. Validation: npm run check still passes for this plugin (OK plugin antianqi/codex-harness-patterns). --- .../antianqi/codex-harness-patterns/README.md | 148 +++++------------- .../codex-harness-patterns/plugin.json | 2 +- .../skills/background-task/SKILL.md | 8 +- .../skills/completion-audit/SKILL.md | 8 +- .../skills/context-pressure-compact/SKILL.md | 8 +- .../skills/delegate-with-context/SKILL.md | 8 +- .../skills/error-recovery-strategy/SKILL.md | 8 +- .../skills/fork-context-decision/SKILL.md | 8 +- .../skills/goal-persistence/SKILL.md | 8 +- .../skills/goal-token-budgeting/SKILL.md | 8 +- .../skills/model-router/SKILL.md | 8 +- .../skills/parallel-fanout/SKILL.md | 8 +- .../skills/plan-stream-emit/SKILL.md | 8 +- .../skills/retry-with-backoff/SKILL.md | 8 +- .../skills/review-mode/SKILL.md | 8 +- .../skills/session-handoff/SKILL.md | 8 +- .../skills/streaming-output-reader/SKILL.md | 8 +- .../skills/subagent-family-tracking/SKILL.md | 8 +- .../skills/tool-output-budget/SKILL.md | 8 +- .../skills/world-state-tracking/SKILL.md | 8 +- 20 files changed, 149 insertions(+), 145 deletions(-) diff --git a/plugins/antianqi/codex-harness-patterns/README.md b/plugins/antianqi/codex-harness-patterns/README.md index 32da00b..4b658fe 100644 --- a/plugins/antianqi/codex-harness-patterns/README.md +++ b/plugins/antianqi/codex-harness-patterns/README.md @@ -8,56 +8,28 @@ original goal, paying main-model prices for cheap-model work, losing track of wh sub-agent is doing what, failing on transient errors without a budget, reading streaming output without filling context, or losing work at session end. -## The problem - -Long agentic sessions fail for predictable reasons: - -- **Tool outputs explode** — `cat` on a 5,000-line log, or `curl` returning 1 MB of HTML, can fill - the context in a single step. -- **Context drift** — after 30+ tool calls the model has lost track of the original goal, current - state, and what still needs doing. -- **Serial work** — the model does A, then B, then C, when A, B, C are independent and could - finish in one round trip. -- **No plan** — the model dives into a complex task without first surfacing a structured plan, - so the user cannot course-correct early. -- **No review** — the model writes code, says "done", and ships a defect the user has to find. -- **No proof of done** — the model marks a task complete from memory, not from evidence. -- **Bloated sub-agent briefs** — the model dumps the full conversation history into a `task` - call, paying the token cost twice. -- **No shared state** — long tasks have no persistent ground truth that survives context - compaction, so the model keeps re-deriving "where are we?". -- **Foreground blocks** — the model `bash`es a 5-minute build, blocks the conversation, and - times out. -- **Goal drift** — the original user request gets silently replaced by an inferred goal, and - the agent ends up doing a side quest with confident justification. -- **Model over-spend** — the agent uses the main model for routine lookups and transforms - that a cheap model could handle in a fraction of the time and cost. -- **Sub-agent context over-spend** — the agent gives every sub-agent the full history when a - small brief would do. -- **Lost sub-agents** — the agent spawns 3 children, loses track of which is which, and either - duplicates work or never reads a child's result. -- **Runaway goal cost** — the user sets a token budget for a goal; the agent blows past it - without surfacing the warning. -- **Silent retry** — the agent retries a `deterministic` error (permission denied, file not - found) three times in a row, burning the same error each time. -- **Streaming overflow** — the agent reads an unbounded stream in one go, filling the - context with raw bytes instead of a summary. -- **Session loss** — the user steps away; next session starts with no idea what was in - progress. - -OpenAI's Codex harness solves each of these with specific code (see -[`codex-rs/core/src/compact.rs`](https://github.com/openai/codex/blob/main/codex-rs/core/src/compact.rs), -[`utils/output-truncation/`](https://github.com/openai/codex/tree/main/codex-rs/utils/output-truncation), -[`session/turn.rs::run_turn`](https://github.com/openai/codex/blob/main/codex-rs/core/src/session/turn.rs), -[`context/world_state.rs`](https://github.com/openai/codex/blob/main/codex-rs/core/src/context/world_state.rs), -[`ext/goal/templates/goals/continuation.md`](https://github.com/openai/codex/blob/main/codex-rs/ext/goal/templates/goals/continuation.md), -[`ext/goal/src/accounting.rs`](https://github.com/openai/codex/blob/main/codex-rs/ext/goal/src/accounting.rs), -[`model-provider-info/`](https://github.com/openai/codex/tree/main/codex-rs/model-provider-info), -[`agent-graph-store/`](https://github.com/openai/codex/tree/main/codex-rs/agent-graph-store), -[`code-mode/src/grpc_session/reconnect.rs`](https://github.com/openai/codex/blob/main/codex-rs/code-mode/src/grpc_session/reconnect.rs), -[`state/src/runtime/recovery.rs`](https://github.com/openai/codex/blob/main/codex-rs/state/src/runtime/recovery.rs)) -and reports a 3× score lift on ARC-AGI-3 with the same model, just by changing the harness. -This Plugin packages those patterns as portable Skills. +## v0.6.1 changelog (this release) + +### 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 @@ -108,47 +80,24 @@ 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 | -| 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 | -| 3 | `parallel-fanout` | The user task is clearly decomposable into 2+ independent sub-tasks (independent files, independent probes, independent analyses). | v0.1.0 → v1.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 | -| 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 | -| 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.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 | -| 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 | -| 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 | -| 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 | -| 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 | -| 12 | `fork-context-decision` | About to call `task` to hand off a sub-task. Decides how much parent context to give the sub-agent via the `fork_turns` parameter. | v0.4.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 | -| 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 | -| 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 (new)** | -| 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 (new)** | -| 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 (new)** | -| 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 (new)** | - -## v0.6.0 changelog - -### Added - -- `error-recovery-strategy` Skill — 4-bucket classification (transient / deterministic / stale / unknown) → 5-action decision tree (retry / switch / fallback / refresh-then-retry / ask-user / skip). Mirrors the `code-mode` reconnect philosophy and the `MultiAgentMode::ExplicitRequestOnly` opt-in principle. -- `retry-with-backoff` Skill — explicit retry policy: max 3 attempts, base 2s, max 30s, full jitter, 60s total budget. Respects `Retry-After`. Hard ceiling, no silent extension. Always escalates on exhaustion. -- `streaming-output-reader` Skill — read in bounded chunks (head / tail / grep), write a cumulative summary, stop after at most 3 reads. Mirrors the `WebsocketSession.last_request` incremental pattern and the `unified_exec` background-command pattern. -- `session-handoff` Skill — at session end, write a structured handoff file (verbatim goal, state file references, done/in-progress items, next concrete step, critical paths, "might be wrong" risks). Mirrors `state/runtime/recovery.rs` (DB-backed resume) and the `rollout_migration_state` migration. - -Total Skills: 18 (14 from v0.5.0 + 4 new). - -## v0.5.0 changelog (prior) - -### Added - -- `subagent-family-tracking` Skill. -- `goal-token-budgeting` Skill. - -### Updated - -- `context-pressure-compact` v1.0. -- `delegate-with-context` v1.0. +| 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.0.1 | +| 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.0.1 | +| 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 → 0.1.1 | +| 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 → 0.3.1 | +| 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 via the `fork_turns` parameter. | v0.4.0 → 0.4.1 | +| 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 | ## Requirements @@ -158,23 +107,6 @@ Total Skills: 18 (14 from v0.5.0 + 4 new). - **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. -## Capabilities & permissions - -- **Read-only by default** (these Skills only change how the agent shapes its own output and - tool calls). -- **No file modification outside the agent's existing write surface.** The Skills may instruct - the agent to use `write` / `edit` / `bash` to persist a compact summary, a plan file, a - world-state file, a goal file, a family file, a handoff file, or a usage log, but only on - paths the user already authorised through the active session. -- **No sub-agent launch without user intent.** `parallel-fanout`, `delegate-with-context`, and - `fork-context-decision` instruct the agent to use `task` for fan-out / delegation, but only - when the user task is independently decomposable **and** the user has opted in to - multi-agent work. -- **No model switching that the harness does not support.** `model-router` only works if the - underlying `task` tool exposes `model_config_id` (or equivalent). If the harness does not - support model routing, the Skill degrades to "classify the sub-task" and the model choice - follows whatever default the harness provides. - ## Data and network - **No network access.** This Plugin adds Skills only; it does not call out. diff --git a/plugins/antianqi/codex-harness-patterns/plugin.json b/plugins/antianqi/codex-harness-patterns/plugin.json index e16e7ea..4d9f800 100644 --- a/plugins/antianqi/codex-harness-patterns/plugin.json +++ b/plugins/antianqi/codex-harness-patterns/plugin.json @@ -1,7 +1,7 @@ { "$schema": "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json", "name": "codex-harness-patterns", - "version": "0.6.0", + "version": "0.6.1", "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, and session handoff. 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, or hand off a session cleanly.", "author": { "name": "antianqi", diff --git a/plugins/antianqi/codex-harness-patterns/skills/background-task/SKILL.md b/plugins/antianqi/codex-harness-patterns/skills/background-task/SKILL.md index 848a51b..5a00037 100644 --- a/plugins/antianqi/codex-harness-patterns/skills/background-task/SKILL.md +++ b/plugins/antianqi/codex-harness-patterns/skills/background-task/SKILL.md @@ -1,11 +1,15 @@ --- name: background-task -description: "Run a long-running command (dev server, build, watcher, test loop, file sync) as a background task that the agent can poll, steer, and shut down, instead of blocking the conversation on it. Use when a command is expected to take > 30 seconds, when the user wants to keep talking while it runs, when you need to start something and then check on it later in the same session, or when an earlier foreground call already failed with a timeout. Mirrors codex-rs CleanBackgroundTerminals and unified_exec in core/src/unified_exec/." +description: | + Run long-running command as background task instead of blocking conversation. + USE WHEN: command expected > 30s, dev server / build / watcher / test loop / `tail -f` / long npm/cargo/make output, user said "in the background" / "don't block" / "后台" / "并行跑" / "kick off", earlier foreground call timed out, want to keep talking while command runs, file sync / `fswatch` / live-reload. + TRIGGER PHRASES: "后台", "background", "in the background", "并行跑", "don't block", "继续做别的事", "kick off the build", "start the server", "跑着不用等", "background task", "起个 server", "watch 一下". + SKIP WHEN: command is short (<30s), output is the deliverable (read in one shot), destructive command needing exit code. license: Apache-2.0 compatibility: Requires MiniMax Code with Agent Plugins 1.0 support. metadata: author: antianqi - version: "0.1.0" + version: "0.1.1" inspired-by: https://github.com/openai/codex/blob/main/codex-rs/core/src/unified_exec/ --- diff --git a/plugins/antianqi/codex-harness-patterns/skills/completion-audit/SKILL.md b/plugins/antianqi/codex-harness-patterns/skills/completion-audit/SKILL.md index 10c33cc..2ded362 100644 --- a/plugins/antianqi/codex-harness-patterns/skills/completion-audit/SKILL.md +++ b/plugins/antianqi/codex-harness-patterns/skills/completion-audit/SKILL.md @@ -1,11 +1,15 @@ --- name: completion-audit -description: "Before marking a non-trivial task complete (or telling the user it's done), treat completion as unproven and verify it against the actual current state. Derive requirements from the objective, identify the authoritative evidence for each, inspect that evidence, and only declare done when every requirement is satisfied. Use whenever the agent is about to say 'done' / 'complete' / 'ship it' / 'I finished' on a non-trivial task, especially when there is an active thread goal. Mirrors the completion audit section of the Codex goal continuation template (ext/goal/templates/goals/continuation.md)." +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.0" + version: "0.1.1" inspired-by: https://github.com/openai/codex/blob/main/codex-rs/ext/goal/templates/goals/continuation.md --- 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 index 04cfb6c..d8653c3 100644 --- a/plugins/antianqi/codex-harness-patterns/skills/context-pressure-compact/SKILL.md +++ b/plugins/antianqi/codex-harness-patterns/skills/context-pressure-compact/SKILL.md @@ -1,11 +1,15 @@ --- name: context-pressure-compact -description: "Compress a long-running multi-step task into a structured state summary before continuing, so the agent can keep going without losing track of the original goal. Use when the active `todowrite` exceeds 5 items, after ~20 tool calls, when the user says 'compact' / 'summarize so far' / 'we need to refocus', or when context usage is visibly heavy. Mirrors codex-rs/core/src/compact.rs::run_pre_sampling_compact and the v2 64K retention budget (RETAINED_MESSAGE_TOKEN_BUDGET)." +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.0" + 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." --- 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 index 3a7b522..0e0e14e 100644 --- a/plugins/antianqi/codex-harness-patterns/skills/delegate-with-context/SKILL.md +++ b/plugins/antianqi/codex-harness-patterns/skills/delegate-with-context/SKILL.md @@ -1,11 +1,15 @@ --- name: delegate-with-context -description: "When delegating a sub-task to another agent (via `task`), prepare a minimal-context brief instead of dumping the full conversation history. Use when handing off a sub-task boundary, when a sub-agent needs the user's goal + the specific boundary + the pass condition + the minimal inputs, and when the conversation history is large enough that forwarding it all would waste tokens. Mirrors codex-rs InterAgentCommunication in Op / CollabAgentSpawnBegin, plus the explicit 'Message Type / Task name / Sender / Payload' message envelope from V2 multi-agent protocol." +description: | + Write a minimal-context brief for `task()` instead of dumping full history. + USE WHEN: about to call `task()` to hand off sub-task, full conversation history > 30 turns, sub-task has clear boundary, find yourself wanting to write "see above" / "上面对话", sub-task is non-trivial, user said "派个子 agent" / "spawn agent" / "delegate" / "fork 出去" / "sub-agent 干". + TRIGGER PHRASES: "派个子 agent", "spawn agent", "让子 agent 干", "delegate", "sub-agent", "把任务交出去", "fork 出去", "background task", "派发", "子 agent 干", "子任务". + SKIP WHEN: sub-agent needs verbatim context (rare; usually `read` / `grep` is faster), sub-task boundary is fuzzy (decompose first via `plan-stream-emit`), work is so small brief would be longer than the work itself. license: Apache-2.0 compatibility: Requires MiniMax Code with Agent Plugins 1.0 support. metadata: author: antianqi - version: "1.0.0" + version: "1.0.1" inspired-by: https://github.com/openai/codex/blob/main/codex-rs/protocol/src/protocol.rs (Op::InterAgentCommunication, CollabAgentSpawnBegin) and core/src/session/multi_agents.rs changes-from-v0.2.0: "Added the message envelope format (Message Type / Task name / Sender / Payload) from P-20 V2; added explicit 'this is the sub-agent return path' section; cross-referenced fork-context-decision for fork_turns choice." --- 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 index 575b24b..dfb4d05 100644 --- a/plugins/antianqi/codex-harness-patterns/skills/error-recovery-strategy/SKILL.md +++ b/plugins/antianqi/codex-harness-patterns/skills/error-recovery-strategy/SKILL.md @@ -1,11 +1,15 @@ --- name: error-recovery-strategy -description: "When a tool call, sub-agent task, or external operation fails, decide between retry / switch / fallback / ask-user / skip. Use whenever a tool returns a non-success result, a sub-agent reports failure, or an exception escapes from a call. The decision must be explicit, not reflexive. Mirrors the resilience patterns in codex-rs `code-mode/src/grpc_session/reconnect.rs` (binding-replacement over re-retry) and the `MultiAgentMode::ExplicitRequestOnly` philosophy (don't auto-recover without user signal)." +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.0" + version: "0.1.1" 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 --- 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 index bc3c521..f6b3cd6 100644 --- a/plugins/antianqi/codex-harness-patterns/skills/fork-context-decision/SKILL.md +++ b/plugins/antianqi/codex-harness-patterns/skills/fork-context-decision/SKILL.md @@ -1,11 +1,15 @@ --- name: fork-context-decision -description: "When spawning a sub-agent, decide how much parent context to give it via the `fork_turns` parameter. Use every time you call `task` (or equivalent) to hand off work — the choice between `all` / `none` / a positive integer is one of the largest cost levers in multi-agent work. Mirrors the `fork_turns` semantics in codex-rs/ext/goal/src/multi_agents.rs (V2 multi-agent protocol)." +description: | + Pick `fork_turns` = all / N / none for sub-agent context size. + 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" / "传 history" / "just the brief" / "fork 0" / "fork all" / "不用 fork" / "不要带 context". + TRIGGER PHRASES: "fork 多少", "give it the full history", "不用 fork", "传 history", "just the brief", "不要带 context", "fork 0", "fork all", "传全部对话", "不带 context". + SKIP WHEN: sub-agent tool does not support `fork_turns`, already decided `none` (no decision to make). license: Apache-2.0 compatibility: Requires MiniMax Code with Agent Plugins 1.0 support. metadata: author: antianqi - version: "0.1.0" + version: "0.1.1" inspired-by: https://github.com/openai/codex/blob/main/codex-rs/core/src/session/multi_agents.rs --- diff --git a/plugins/antianqi/codex-harness-patterns/skills/goal-persistence/SKILL.md b/plugins/antianqi/codex-harness-patterns/skills/goal-persistence/SKILL.md index b3e598d..2443b00 100644 --- a/plugins/antianqi/codex-harness-patterns/skills/goal-persistence/SKILL.md +++ b/plugins/antianqi/codex-harness-patterns/skills/goal-persistence/SKILL.md @@ -1,11 +1,15 @@ --- name: goal-persistence -description: "Maintain an explicit north-star goal for the whole thread that survives compactions and detects drift. Use at the start of any non-trivial task (one-time set), after every user redirection (one-time update), and at every `context-pressure-compact` boundary (one-line alignment check). Before declaring done, run a completion audit (see also `completion-audit` Skill). Mirrors codex-rs `Op::SetThreadMemoryMode` + `EventMsg::ThreadGoalUpdated` in protocol/src/protocol.rs and the continuation template in ext/goal/templates/goals/continuation.md." +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.0" + 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." --- 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 index 829a356..8e40fb6 100644 --- a/plugins/antianqi/codex-harness-patterns/skills/goal-token-budgeting/SKILL.md +++ b/plugins/antianqi/codex-harness-patterns/skills/goal-token-budgeting/SKILL.md @@ -1,11 +1,15 @@ --- name: goal-token-budgeting -description: "When the user sets an explicit token budget on a goal, track the running usage and report it on completion. Use whenever `goal-persistence` is active and the user provided a `token_budget` (either initially or via `Op::SetThreadMemoryMode`). Mirrors codex-rs `ext/goal/src/accounting.rs` (GoalAccountingState) and the 'Tokens used / Token budget / Tokens remaining' section of the goal continuation template." +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.0" + 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 --- diff --git a/plugins/antianqi/codex-harness-patterns/skills/model-router/SKILL.md b/plugins/antianqi/codex-harness-patterns/skills/model-router/SKILL.md index d776956..623eeb6 100644 --- a/plugins/antianqi/codex-harness-patterns/skills/model-router/SKILL.md +++ b/plugins/antianqi/codex-harness-patterns/skills/model-router/SKILL.md @@ -1,11 +1,15 @@ --- name: model-router -description: "Before delegating a sub-task (via `task`), assess the sub-task's complexity and pick the model config that matches it: cheap model for routine lookups, main model for complex work. Use every time you call `task` and the sub-task is non-trivial, and any time you are about to spend the main model on work a cheap model could do. Mirrors codex-rs `model-provider-info` + `models-manager` + the routing layer that lets sub-agents run on cheaper models." +description: | + Classify sub-task complexity (cheap / medium / main) and pick matching `model_config_id`. + USE WHEN: about to call `task()` for non-trivial sub-task, about to spend main model on work cheap model could do, "do this with the cheap model" / "用便宜模型" / "不要用主模型" / "sub-task 不重" / "small task", sub-task is routine lookup / reformat / list / reformat-only, "this is just a grep" / "this is just a reformat" / "小任务". + TRIGGER PHRASES: "用便宜模型", "cheap model", "use the cheap model", "小任务用便宜模型", "不要用主模型", "用本地模型", "sub-task 不重", "小任务", "this is just a", "小 case 用便宜". + SKIP WHEN: sub-task IS the main task (no delegation), sub-agent tool does not support `model_config_id`, sub-task is genuinely synthesis / design / cross-file reasoning. license: Apache-2.0 compatibility: Requires MiniMax Code with Agent Plugins 1.0 support. metadata: author: antianqi - version: "0.1.0" + version: "0.1.1" inspired-by: https://github.com/openai/codex/blob/main/codex-rs/model-provider-info/ and codex-rs/models-manager/ --- diff --git a/plugins/antianqi/codex-harness-patterns/skills/parallel-fanout/SKILL.md b/plugins/antianqi/codex-harness-patterns/skills/parallel-fanout/SKILL.md index 6dff2f6..84f44eb 100644 --- a/plugins/antianqi/codex-harness-patterns/skills/parallel-fanout/SKILL.md +++ b/plugins/antianqi/codex-harness-patterns/skills/parallel-fanout/SKILL.md @@ -1,11 +1,15 @@ --- name: parallel-fanout -description: "Decompose a clearly independent task into 2+ parallel sub-tasks and dispatch them with `task` in one round trip, then aggregate. Use when the user task can be split along a clean boundary (independent files, independent probes, independent analyses) and serial execution would take materially longer. Mirrors the `spawn_agent` tool in codex-rs's V2 multi-agent protocol, where the spawn is **explicit and opt-in** (not auto)." +description: | + Dispatch 2+ independent sub-tasks in parallel via `task` and aggregate. + USE WHEN: 2+ independent sub-tasks, each bounded and well-defined, serial would take 2x longer than longest sub-task, user said "并行" / "parallel" / "fan out" / "spawn agents", multiple independent files/probes/analyses, "for each of A/B/C" / "分头做" / "拆开". + TRIGGER PHRASES: "并行", "parallel", "fan out", "spawn agents", "分头做", "一起做", "for each", "分别", "拆开并行", "一起跑". + SKIP WHEN: sub-tasks share state, sub-tasks depend on each other's output, total work is tiny (< 3 edits), user said "one by one" / "step by step" / "sequentially" / "一个一个来". license: Apache-2.0 compatibility: Requires MiniMax Code with Agent Plugins 1.0 support. metadata: author: antianqi - version: "1.0.0" + version: "1.0.1" inspired-by: https://github.com/openai/codex/blob/main/codex-rs/core/src/session/multi_agents.rs and core/src/thread_manager.rs changes-from-v0.1.0: "Added explicit-spawn principle (P-20: spawn is opt-in, not auto); added `max_concurrency` awareness; cross-referenced `fork-context-decision` for per-sub-task cost control; cross-referenced `delegate-with-context` for the 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 index 0df5934..6b48205 100644 --- a/plugins/antianqi/codex-harness-patterns/skills/plan-stream-emit/SKILL.md +++ b/plugins/antianqi/codex-harness-patterns/skills/plan-stream-emit/SKILL.md @@ -1,11 +1,15 @@ --- name: plan-stream-emit -description: "Before touching files on a non-trivial task, emit a structured plan as `todowrite` items and surface the plan to the user for early course-correction. Use when the user request is multi-step, has any ambiguity, or would take more than 3 tool calls to complete. Mirrors codex-rs `PlanUpdate` / `PlanDelta` events and the Op::PlanUpdate wire event." +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.0" + version: "0.1.1" inspired-by: https://github.com/openai/codex/blob/main/codex-rs/protocol/src/protocol.rs (PlanUpdate / PlanDelta) --- 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 index 8896373..1d4d6db 100644 --- a/plugins/antianqi/codex-harness-patterns/skills/retry-with-backoff/SKILL.md +++ b/plugins/antianqi/codex-harness-patterns/skills/retry-with-backoff/SKILL.md @@ -1,11 +1,15 @@ --- name: retry-with-backoff -description: "When a `transient` error has been classified (see `error-recovery-strategy`), execute the retry with explicit backoff policy: max attempts, base delay, exponential growth, jitter, and a hard ceiling on total time. Use every time you decide to retry. Mirrors the policy choices implicit in codex-rs `code-mode/src/grpc_session/reconnect.rs::get_or_open_binding` (no delay between generations, but bounded by `Semaphore(1)` concurrency) and the W3C retry semantics." +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.0" + version: "0.1.1" inspired-by: https://github.com/openai/codex/blob/main/codex-rs/code-mode/src/grpc_session/reconnect.rs --- diff --git a/plugins/antianqi/codex-harness-patterns/skills/review-mode/SKILL.md b/plugins/antianqi/codex-harness-patterns/skills/review-mode/SKILL.md index 3cf39ea..b8f775d 100644 --- a/plugins/antianqi/codex-harness-patterns/skills/review-mode/SKILL.md +++ b/plugins/antianqi/codex-harness-patterns/skills/review-mode/SKILL.md @@ -1,11 +1,15 @@ --- name: review-mode -description: "After completing a non-trivial chunk of work (a function, a file, a feature, a config change), switch to a critical-reviewer mode and verify the work before declaring it done. Use when a sub-task boundary is reached, when the user says 'review this' / 'double-check' / 'is this right', or before reporting 'done' on anything the user will rely on. Mirrors codex-rs EnteredReviewMode / ExitedReviewMode events in protocol/src/protocol.rs." +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.0" + version: "0.1.1" inspired-by: https://github.com/openai/codex/blob/main/codex-rs/protocol/src/protocol.rs (EnteredReviewModeEvent / ExitedReviewModeEvent) --- diff --git a/plugins/antianqi/codex-harness-patterns/skills/session-handoff/SKILL.md b/plugins/antianqi/codex-harness-patterns/skills/session-handoff/SKILL.md index d039821..2e28eab 100644 --- a/plugins/antianqi/codex-harness-patterns/skills/session-handoff/SKILL.md +++ b/plugins/antianqi/codex-harness-patterns/skills/session-handoff/SKILL.md @@ -1,11 +1,15 @@ --- name: session-handoff -description: "At the end of a session, write a state file that lets the next session pick up exactly where this one left off. Use when a session is ending (user says 'done for today' / context is about to compact / time is up) and there is non-trivial work in progress. Mirrors codex-rs `state/migrations/0047_rollout_migration_state.sql` (explicit migration state) and the `state/runtime/recovery.rs` pattern (DB-backed resume on crash)." +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.0" + 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 --- 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 index 6fbbdf7..e54e235 100644 --- a/plugins/antianqi/codex-harness-patterns/skills/streaming-output-reader/SKILL.md +++ b/plugins/antianqi/codex-harness-patterns/skills/streaming-output-reader/SKILL.md @@ -1,11 +1,15 @@ --- name: streaming-output-reader -description: "Read long streaming responses (SSE / WebSocket chunks / `tail -f` / long-running commands) in a way that does not block, does not buffer the whole thing in context, and does not miss output. Use whenever a tool returns a stream that is too long for a single read, or whenever a single read would force the agent to wait instead of doing other work. Mirrors the `WebsocketSession.last_request` incremental pattern in codex-rs/core/src/client.rs and the `unified_exec` background-command pattern in codex-rs/core/src/unified_exec/." +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.0" + version: "0.1.1" inspired-by: https://github.com/openai/codex/blob/main/codex-rs/core/src/client.rs and core/src/unified_exec/ --- 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 index 70fb1cd..377fb1c 100644 --- a/plugins/antianqi/codex-harness-patterns/skills/subagent-family-tracking/SKILL.md +++ b/plugins/antianqi/codex-harness-patterns/skills/subagent-family-tracking/SKILL.md @@ -1,11 +1,15 @@ --- name: subagent-family-tracking -description: "Track the parent/child thread tree of sub-agents you have spawned, so you know who is alive, who has finished, and which siblings share context. Use every time you spawn a sub-agent and the task may fan out (multiple children) or chain (a child spawning its own children). Mirrors codex-rs `agent-graph-store` (parent→child edges with Open/Closed status) plus the `SessionSource::SubAgent(SubAgentSource::ThreadSpawn)` marker." +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.0" + version: "0.1.1" inspired-by: https://github.com/openai/codex/blob/main/codex-rs/agent-graph-store/ --- 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 index 804a929..54af6d1 100644 --- a/plugins/antianqi/codex-harness-patterns/skills/tool-output-budget/SKILL.md +++ b/plugins/antianqi/codex-harness-patterns/skills/tool-output-budget/SKILL.md @@ -1,11 +1,15 @@ --- name: tool-output-budget -description: "Truncate oversized tool output (large logs, JSON arrays, fetched HTML, minified code, noisy `cat` results) so it does not blow the agent's context window. Use when a tool returns more than ~3000 tokens, or contains any line longer than ~500 characters, or returns structured data the agent will only sample. Mirrors codex-rs/utils/output-truncation." +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.0" + version: "0.1.1" inspired-by: https://github.com/openai/codex/tree/main/codex-rs/utils/output-truncation --- 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 index 01f5746..6ce8f47 100644 --- a/plugins/antianqi/codex-harness-patterns/skills/world-state-tracking/SKILL.md +++ b/plugins/antianqi/codex-harness-patterns/skills/world-state-tracking/SKILL.md @@ -1,11 +1,15 @@ --- name: world-state-tracking -description: "Track the running state of a long task (goal, decisions, blockers, next step, key paths) in a single dedicated file that survives context compaction. Use when the task is long enough that `todowrite` alone is too thin, when the user keeps referring to 'where we are', when the agent has lost the thread, or at every `context-pressure-compact` boundary. Mirrors codex-rs `WorldState` struct in core/src/context/world_state.rs." +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.0" + version: "0.1.1" inspired-by: https://github.com/openai/codex/blob/main/codex-rs/core/src/context/world_state.rs --- From 4a7b838a8861643d20c8c58cc82b7cde891e135f Mon Sep 17 00:00:00 2001 From: antianqi Date: Mon, 24 Aug 2026 18:31:45 +0800 Subject: [PATCH 10/49] v0.6.2: catalog corrections (6 misjudgments) + 2-3 month roadmap --- .../antianqi/codex-harness-patterns/README.md | 35 ++++++++++++++++++- .../codex-harness-patterns/plugin.json | 2 +- 2 files changed, 35 insertions(+), 2 deletions(-) diff --git a/plugins/antianqi/codex-harness-patterns/README.md b/plugins/antianqi/codex-harness-patterns/README.md index 4b658fe..4c5ff7c 100644 --- a/plugins/antianqi/codex-harness-patterns/README.md +++ b/plugins/antianqi/codex-harness-patterns/README.md @@ -8,7 +8,40 @@ original goal, paying main-model prices for cheap-model work, losing track of wh sub-agent is doing what, failing on transient errors without a budget, reading streaming output without filling context, or losing work at session end. -## v0.6.1 changelog (this release) +## v0.6.2 changelog (this release) + +> **类型**: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 diff --git a/plugins/antianqi/codex-harness-patterns/plugin.json b/plugins/antianqi/codex-harness-patterns/plugin.json index 4d9f800..7480f20 100644 --- a/plugins/antianqi/codex-harness-patterns/plugin.json +++ b/plugins/antianqi/codex-harness-patterns/plugin.json @@ -1,7 +1,7 @@ { "$schema": "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json", "name": "codex-harness-patterns", - "version": "0.6.1", + "version": "0.6.2", "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, and session handoff. 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, or hand off a session cleanly.", "author": { "name": "antianqi", From 901e2a13db6f8dd252a45003172bf544d1c4f251 Mon Sep 17 00:00:00 2001 From: antianqi Date: Mon, 24 Aug 2026 18:37:22 +0800 Subject: [PATCH 11/49] =?UTF-8?q?v0.6.3:=20research=20milestone=20?= =?UTF-8?q?=E2=80=94=20Phase=201=20Week=201=20(memories/=20deep-dive)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../antianqi/codex-harness-patterns/README.md | 30 ++++++++++++++++++- .../codex-harness-patterns/plugin.json | 2 +- 2 files changed, 30 insertions(+), 2 deletions(-) diff --git a/plugins/antianqi/codex-harness-patterns/README.md b/plugins/antianqi/codex-harness-patterns/README.md index 4c5ff7c..2ab7f32 100644 --- a/plugins/antianqi/codex-harness-patterns/README.md +++ b/plugins/antianqi/codex-harness-patterns/README.md @@ -8,7 +8,35 @@ original goal, paying main-model prices for cheap-model work, losing track of wh sub-agent is doing what, failing on transient errors without a budget, reading streaming output without filling context, or losing work at session end. -## v0.6.2 changelog (this release) +## v0.6.3 changelog (this release) + +> **类型**: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 版本号不变) · **元数据 + 文档更新** diff --git a/plugins/antianqi/codex-harness-patterns/plugin.json b/plugins/antianqi/codex-harness-patterns/plugin.json index 7480f20..4720dca 100644 --- a/plugins/antianqi/codex-harness-patterns/plugin.json +++ b/plugins/antianqi/codex-harness-patterns/plugin.json @@ -1,7 +1,7 @@ { "$schema": "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json", "name": "codex-harness-patterns", - "version": "0.6.2", + "version": "0.6.3", "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, and session handoff. 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, or hand off a session cleanly.", "author": { "name": "antianqi", From 3616b16d2ada5c80a05ca6863b205a0c75376427 Mon Sep 17 00:00:00 2001 From: antianqi Date: Mon, 24 Aug 2026 23:19:35 +0800 Subject: [PATCH 12/49] =?UTF-8?q?v0.6.4:=20research=20milestone=20?= =?UTF-8?q?=E2=80=94=20Phase=201=20Week=202=20(skills/=20deep-dive)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../antianqi/codex-harness-patterns/README.md | 36 ++++++++++++++++++- .../codex-harness-patterns/plugin.json | 2 +- 2 files changed, 36 insertions(+), 2 deletions(-) diff --git a/plugins/antianqi/codex-harness-patterns/README.md b/plugins/antianqi/codex-harness-patterns/README.md index 2ab7f32..43251bb 100644 --- a/plugins/antianqi/codex-harness-patterns/README.md +++ b/plugins/antianqi/codex-harness-patterns/README.md @@ -8,7 +8,41 @@ original goal, paying main-model prices for cheap-model work, losing track of wh sub-agent is doing what, failing on transient errors without a budget, reading streaming output without filling context, or losing work at session end. -## v0.6.3 changelog (this release) +## v0.6.4 changelog (this release) + +> **类型**: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 完成) diff --git a/plugins/antianqi/codex-harness-patterns/plugin.json b/plugins/antianqi/codex-harness-patterns/plugin.json index 4720dca..9015772 100644 --- a/plugins/antianqi/codex-harness-patterns/plugin.json +++ b/plugins/antianqi/codex-harness-patterns/plugin.json @@ -1,7 +1,7 @@ { "$schema": "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json", "name": "codex-harness-patterns", - "version": "0.6.3", + "version": "0.6.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, and session handoff. 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, or hand off a session cleanly.", "author": { "name": "antianqi", From cfadb535478faa77bf94f0a59b5f217a251fa1a3 Mon Sep 17 00:00:00 2001 From: antianqi Date: Mon, 24 Aug 2026 23:29:22 +0800 Subject: [PATCH 13/49] =?UTF-8?q?v0.6.5:=20research=20milestone=20?= =?UTF-8?q?=E2=80=94=20Phase=201=20Week=203=20(thread-store/=20deep-dive)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../antianqi/codex-harness-patterns/README.md | 42 ++++++++++++++++++- .../codex-harness-patterns/plugin.json | 2 +- 2 files changed, 42 insertions(+), 2 deletions(-) diff --git a/plugins/antianqi/codex-harness-patterns/README.md b/plugins/antianqi/codex-harness-patterns/README.md index 43251bb..85de5f0 100644 --- a/plugins/antianqi/codex-harness-patterns/README.md +++ b/plugins/antianqi/codex-harness-patterns/README.md @@ -8,7 +8,47 @@ original goal, paying main-model prices for cheap-model work, losing track of wh sub-agent is doing what, failing on transient errors without a budget, reading streaming output without filling context, or losing work at session end. -## v0.6.4 changelog (this release) +## v0.6.5 changelog (this release) + +> **类型**: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 完成) diff --git a/plugins/antianqi/codex-harness-patterns/plugin.json b/plugins/antianqi/codex-harness-patterns/plugin.json index 9015772..0ee543c 100644 --- a/plugins/antianqi/codex-harness-patterns/plugin.json +++ b/plugins/antianqi/codex-harness-patterns/plugin.json @@ -1,7 +1,7 @@ { "$schema": "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json", "name": "codex-harness-patterns", - "version": "0.6.4", + "version": "0.6.5", "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, and session handoff. 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, or hand off a session cleanly.", "author": { "name": "antianqi", From ab97ec794b2a3f170a2c545d797a861acf75bf4e Mon Sep 17 00:00:00 2001 From: antianqi Date: Mon, 24 Aug 2026 23:37:33 +0800 Subject: [PATCH 14/49] =?UTF-8?q?v0.7.0:=20research=20milestone=20?= =?UTF-8?q?=E2=80=94=20Phase=201=20completion=20(5=20core=20crates)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../antianqi/codex-harness-patterns/README.md | 45 ++++++++++++++++++- .../codex-harness-patterns/plugin.json | 2 +- 2 files changed, 45 insertions(+), 2 deletions(-) diff --git a/plugins/antianqi/codex-harness-patterns/README.md b/plugins/antianqi/codex-harness-patterns/README.md index 85de5f0..0efc807 100644 --- a/plugins/antianqi/codex-harness-patterns/README.md +++ b/plugins/antianqi/codex-harness-patterns/README.md @@ -8,7 +8,50 @@ original goal, paying main-model prices for cheap-model work, losing track of wh sub-agent is doing what, failing on transient errors without a budget, reading streaming output without filling context, or losing work at session end. -## v0.6.5 changelog (this release) +## v0.7.0 changelog (this release) + +> **类型**:**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 完成) diff --git a/plugins/antianqi/codex-harness-patterns/plugin.json b/plugins/antianqi/codex-harness-patterns/plugin.json index 0ee543c..c60c866 100644 --- a/plugins/antianqi/codex-harness-patterns/plugin.json +++ b/plugins/antianqi/codex-harness-patterns/plugin.json @@ -1,7 +1,7 @@ { "$schema": "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json", "name": "codex-harness-patterns", - "version": "0.6.5", + "version": "0.7.0", "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, and session handoff. 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, or hand off a session cleanly.", "author": { "name": "antianqi", From 15f4d41c3cb7f6da032bd6a2157d3b06208e096a Mon Sep 17 00:00:00 2001 From: antianqi Date: Tue, 25 Aug 2026 00:23:34 +0800 Subject: [PATCH 15/49] =?UTF-8?q?v0.7.1:=20research=20milestone=20?= =?UTF-8?q?=E2=80=94=20Phase=202=20Week=205=20(agent=20+=20session=20deep-?= =?UTF-8?q?dive)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../antianqi/codex-harness-patterns/README.md | 39 ++++++++++++++++++- .../codex-harness-patterns/plugin.json | 2 +- 2 files changed, 39 insertions(+), 2 deletions(-) diff --git a/plugins/antianqi/codex-harness-patterns/README.md b/plugins/antianqi/codex-harness-patterns/README.md index 0efc807..750417c 100644 --- a/plugins/antianqi/codex-harness-patterns/README.md +++ b/plugins/antianqi/codex-harness-patterns/README.md @@ -8,7 +8,44 @@ original goal, paying main-model prices for cheap-model work, losing track of wh sub-agent is doing what, failing on transient errors without a budget, reading streaming output without filling context, or losing work at session end. -## v0.7.0 changelog (this release) +## v0.7.1 changelog (this release) + +> **类型**: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 主体不变** diff --git a/plugins/antianqi/codex-harness-patterns/plugin.json b/plugins/antianqi/codex-harness-patterns/plugin.json index c60c866..0681e22 100644 --- a/plugins/antianqi/codex-harness-patterns/plugin.json +++ b/plugins/antianqi/codex-harness-patterns/plugin.json @@ -1,7 +1,7 @@ { "$schema": "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json", "name": "codex-harness-patterns", - "version": "0.7.0", + "version": "0.7.1", "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, and session handoff. 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, or hand off a session cleanly.", "author": { "name": "antianqi", From 2d8fed0db86e9c264da3f9c0046830c831727b08 Mon Sep 17 00:00:00 2001 From: antianqi Date: Tue, 25 Aug 2026 00:29:05 +0800 Subject: [PATCH 16/49] =?UTF-8?q?v0.7.2:=20research=20milestone=20?= =?UTF-8?q?=E2=80=94=20Phase=202=20Week=206=20(tools/=20deep-dive)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../antianqi/codex-harness-patterns/README.md | 47 ++++++++++++++++++- .../codex-harness-patterns/plugin.json | 2 +- 2 files changed, 47 insertions(+), 2 deletions(-) diff --git a/plugins/antianqi/codex-harness-patterns/README.md b/plugins/antianqi/codex-harness-patterns/README.md index 750417c..f8b10ce 100644 --- a/plugins/antianqi/codex-harness-patterns/README.md +++ b/plugins/antianqi/codex-harness-patterns/README.md @@ -8,7 +8,52 @@ original goal, paying main-model prices for cheap-model work, losing track of wh sub-agent is doing what, failing on transient errors without a budget, reading streaming output without filling context, or losing work at session end. -## v0.7.1 changelog (this release) +## v0.7.2 changelog (this release) + +> **类型**: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) diff --git a/plugins/antianqi/codex-harness-patterns/plugin.json b/plugins/antianqi/codex-harness-patterns/plugin.json index 0681e22..c3abe53 100644 --- a/plugins/antianqi/codex-harness-patterns/plugin.json +++ b/plugins/antianqi/codex-harness-patterns/plugin.json @@ -1,7 +1,7 @@ { "$schema": "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json", "name": "codex-harness-patterns", - "version": "0.7.1", + "version": "0.7.2", "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, and session handoff. 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, or hand off a session cleanly.", "author": { "name": "antianqi", From 00ffed577433f48becc4b7cf14f3db5afabd1648 Mon Sep 17 00:00:00 2001 From: antianqi Date: Tue, 25 Aug 2026 00:39:36 +0800 Subject: [PATCH 17/49] =?UTF-8?q?v0.7.3:=20research=20milestone=20?= =?UTF-8?q?=E2=80=94=20Phase=202=20Week=207=20(rollout=20+=20models-manage?= =?UTF-8?q?r)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../antianqi/codex-harness-patterns/README.md | 25 ++++++++++++++++++- .../codex-harness-patterns/plugin.json | 2 +- 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/plugins/antianqi/codex-harness-patterns/README.md b/plugins/antianqi/codex-harness-patterns/README.md index f8b10ce..0190d11 100644 --- a/plugins/antianqi/codex-harness-patterns/README.md +++ b/plugins/antianqi/codex-harness-patterns/README.md @@ -8,7 +8,30 @@ original goal, paying main-model prices for cheap-model work, losing track of wh sub-agent is doing what, failing on transient errors without a budget, reading streaming output without filling context, or losing work at session end. -## v0.7.2 changelog (this release) +## v0.7.3 changelog (this release) + +> **类型**: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) diff --git a/plugins/antianqi/codex-harness-patterns/plugin.json b/plugins/antianqi/codex-harness-patterns/plugin.json index c3abe53..4011b60 100644 --- a/plugins/antianqi/codex-harness-patterns/plugin.json +++ b/plugins/antianqi/codex-harness-patterns/plugin.json @@ -1,7 +1,7 @@ { "$schema": "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json", "name": "codex-harness-patterns", - "version": "0.7.2", + "version": "0.7.3", "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, and session handoff. 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, or hand off a session cleanly.", "author": { "name": "antianqi", From 1f9d57446cdc8032e5b6b1a84ed568d687e08200 Mon Sep 17 00:00:00 2001 From: antianqi Date: Tue, 25 Aug 2026 00:40:58 +0800 Subject: [PATCH 18/49] =?UTF-8?q?v0.7.4:=20research=20milestone=20?= =?UTF-8?q?=E2=80=94=20Phase=202=20Week=208=20(protocol=20layer)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../antianqi/codex-harness-patterns/README.md | 22 ++++++++++++++++++- .../codex-harness-patterns/plugin.json | 2 +- 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/plugins/antianqi/codex-harness-patterns/README.md b/plugins/antianqi/codex-harness-patterns/README.md index 0190d11..6cc95f4 100644 --- a/plugins/antianqi/codex-harness-patterns/README.md +++ b/plugins/antianqi/codex-harness-patterns/README.md @@ -8,7 +8,27 @@ original goal, paying main-model prices for cheap-model work, losing track of wh sub-agent is doing what, failing on transient errors without a budget, reading streaming output without filling context, or losing work at session end. -## v0.7.3 changelog (this release) +## v0.7.4 changelog (this release) + +> **类型**: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) diff --git a/plugins/antianqi/codex-harness-patterns/plugin.json b/plugins/antianqi/codex-harness-patterns/plugin.json index 4011b60..2055c04 100644 --- a/plugins/antianqi/codex-harness-patterns/plugin.json +++ b/plugins/antianqi/codex-harness-patterns/plugin.json @@ -1,7 +1,7 @@ { "$schema": "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json", "name": "codex-harness-patterns", - "version": "0.7.3", + "version": "0.7.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, and session handoff. 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, or hand off a session cleanly.", "author": { "name": "antianqi", From d2da1eaf91e66f3e3f22c9594e8338a333a5f9a1 Mon Sep 17 00:00:00 2001 From: antianqi Date: Tue, 25 Aug 2026 00:42:21 +0800 Subject: [PATCH 19/49] =?UTF-8?q?v0.7.5:=20research=20milestone=20?= =?UTF-8?q?=E2=80=94=20Phase=203=20(edge=20crates)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../antianqi/codex-harness-patterns/README.md | 24 ++++++++++++++++++- .../codex-harness-patterns/plugin.json | 2 +- 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/plugins/antianqi/codex-harness-patterns/README.md b/plugins/antianqi/codex-harness-patterns/README.md index 6cc95f4..7977086 100644 --- a/plugins/antianqi/codex-harness-patterns/README.md +++ b/plugins/antianqi/codex-harness-patterns/README.md @@ -8,7 +8,29 @@ original goal, paying main-model prices for cheap-model work, losing track of wh sub-agent is doing what, failing on transient errors without a budget, reading streaming output without filling context, or losing work at session end. -## v0.7.4 changelog (this release) +## v0.7.5 changelog (this release) + +> **类型**: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) diff --git a/plugins/antianqi/codex-harness-patterns/plugin.json b/plugins/antianqi/codex-harness-patterns/plugin.json index 2055c04..335407c 100644 --- a/plugins/antianqi/codex-harness-patterns/plugin.json +++ b/plugins/antianqi/codex-harness-patterns/plugin.json @@ -1,7 +1,7 @@ { "$schema": "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json", "name": "codex-harness-patterns", - "version": "0.7.4", + "version": "0.7.5", "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, and session handoff. 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, or hand off a session cleanly.", "author": { "name": "antianqi", From e2ec486be4f58fdc4354c28eaa1c467f16011eea Mon Sep 17 00:00:00 2001 From: antianqi Date: Tue, 25 Aug 2026 00:46:56 +0800 Subject: [PATCH 20/49] =?UTF-8?q?v1.0.0:=20MAJOR=20=E2=80=94=205=20new=20S?= =?UTF-8?q?kills=20(23=20total),=20full=20agent=20lifecycle=20coverage?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../antianqi/codex-harness-patterns/README.md | 39 ++- .../codex-harness-patterns/plugin.json | 11 +- .../skills/long-term-memory/SKILL.md | 141 +++++++++ .../skills/plugin-author-helper/SKILL.md | 208 ++++++++++++++ .../skills/session-branch-fork/SKILL.md | 272 ++++++++++++++++++ .../skills/skill-auto-select/SKILL.md | 200 +++++++++++++ .../skills/tool-discovery-pattern/SKILL.md | 245 ++++++++++++++++ 7 files changed, 1112 insertions(+), 4 deletions(-) create mode 100644 plugins/antianqi/codex-harness-patterns/skills/long-term-memory/SKILL.md create mode 100644 plugins/antianqi/codex-harness-patterns/skills/plugin-author-helper/SKILL.md create mode 100644 plugins/antianqi/codex-harness-patterns/skills/session-branch-fork/SKILL.md create mode 100644 plugins/antianqi/codex-harness-patterns/skills/skill-auto-select/SKILL.md create mode 100644 plugins/antianqi/codex-harness-patterns/skills/tool-discovery-pattern/SKILL.md diff --git a/plugins/antianqi/codex-harness-patterns/README.md b/plugins/antianqi/codex-harness-patterns/README.md index 7977086..d857dd3 100644 --- a/plugins/antianqi/codex-harness-patterns/README.md +++ b/plugins/antianqi/codex-harness-patterns/README.md @@ -8,7 +8,44 @@ original goal, paying main-model prices for cheap-model work, losing track of wh sub-agent is doing what, failing on transient errors without a budget, reading streaming output without filling context, or losing work at session end. -## v0.7.5 changelog (this release) +## v1.0.0 changelog (this release) 🎉 + +> **类型**:**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 收口) diff --git a/plugins/antianqi/codex-harness-patterns/plugin.json b/plugins/antianqi/codex-harness-patterns/plugin.json index 335407c..212bca6 100644 --- a/plugins/antianqi/codex-harness-patterns/plugin.json +++ b/plugins/antianqi/codex-harness-patterns/plugin.json @@ -1,8 +1,8 @@ { "$schema": "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json", "name": "codex-harness-patterns", - "version": "0.7.5", - "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, and session handoff. 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, or hand off a session cleanly.", + "version": "1.0.0", + "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" @@ -31,6 +31,11 @@ "error-recovery", "retry", "streaming", - "session-handoff" + "session-handoff", + "long-term-memory", + "skill-auto-select", + "plugin-author", + "tool-discovery", + "session-fork" ] } 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..99c274d --- /dev/null +++ b/plugins/antianqi/codex-harness-patterns/skills/long-term-memory/SKILL.md @@ -0,0 +1,141 @@ +--- +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. + +## 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/plugin-author-helper/SKILL.md b/plugins/antianqi/codex-harness-patterns/skills/plugin-author-helper/SKILL.md new file mode 100644 index 0000000..8fc8f82 --- /dev/null +++ b/plugins/antianqi/codex-harness-patterns/skills/plugin-author-helper/SKILL.md @@ -0,0 +1,208 @@ +--- +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. + +## 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/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/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/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. From 5f41ba3db99b6ada1f147489bd951a3c91ac5c97 Mon Sep 17 00:00:00 2001 From: antianqi Date: Tue, 25 Aug 2026 07:44:40 +0800 Subject: [PATCH 21/49] =?UTF-8?q?v1.0.1:=20documentation=20refresh=20?= =?UTF-8?q?=E2=80=94=20OVERVIEW.md=20/=20STATUS.md=20/=20PR=20#18=20title?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../codex-harness-patterns/OVERVIEW.md | 117 +++++++++++++++ .../codex-harness-patterns/PR-STATUS.md | 138 ++++++++++++++++++ .../antianqi/codex-harness-patterns/README.md | 18 ++- .../codex-harness-patterns/plugin.json | 2 +- 4 files changed, 273 insertions(+), 2 deletions(-) create mode 100644 plugins/antianqi/codex-harness-patterns/OVERVIEW.md create mode 100644 plugins/antianqi/codex-harness-patterns/PR-STATUS.md diff --git a/plugins/antianqi/codex-harness-patterns/OVERVIEW.md b/plugins/antianqi/codex-harness-patterns/OVERVIEW.md new file mode 100644 index 0000000..bb26cc8 --- /dev/null +++ b/plugins/antianqi/codex-harness-patterns/OVERVIEW.md @@ -0,0 +1,117 @@ +# codex-harness-patterns — Plugin 总览 + +> 最后更新:2026-08-25 · **v1.0.0** · **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 | 写最小简报,显式 fork_turns,消息信封 | +| 4 | `fork-context-decision` | 调 `task` 派发 | 选 `all`/`N`/`none` 给子 agent 多少 context | +| 5 | `subagent-family-tracking` | 派发了 sub-agent | 跟踪父子线程树 Open/Closed 状态 | +| **执行与状态** | | | | +| 6 | `background-task` | 命令预期 > 30s | 后台化,带 task_name,不阻塞 | +| 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 + model_config_id | +| **质量保证** | | | | +| 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..b5554df --- /dev/null +++ b/plugins/antianqi/codex-harness-patterns/PR-STATUS.md @@ -0,0 +1,138 @@ +# PR 状态 + +> 最后更新:2026-08-24 + +## 当前状态 + +| 项 | 值 | +|---|---| +| **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 | +| **当前版本** | v0.6.1 (patch — frontmatter only) | +| **前一个版本** | v0.6.0 | +| **变更** | +3391 / -0 行,21 个文件 | +| **本地 CI** | ✅ CI: success | +| **第三方 bot** | [code]smith: SKIPPED | +| **安全扫描** | ⏳ CodeQL: 跑过前两次,需要再确认最新 | +| **官方 review** | ⏳ 等维护者 | + +## 版本演进 + +| 版本 | Skills | 主要内容 | 累计 PR 变更 | +|---|---|---|---| +| v0.1.0 | 4 | tool-output-budget / context-pressure-compact / parallel-fanout / plan-stream-emit | +836 | +| v0.2.0 | 8 | + review-mode / delegate-with-context / world-state-tracking / background-task | +1451 | +| v0.3.0 | 10 | + goal-persistence / model-router | +2272 | +| v0.4.0 | 12 | + completion-audit / fork-context-decision;goal-persistence + parallel-fanout 升 v1.0 | +2799 | +| v0.5.0 | 14 | + subagent-family-tracking / goal-token-budgeting;context-pressure-compact + delegate-with-context 升 v1.0 | +3387 | +| v0.6.0 | 18 | + error-recovery / retry / streaming / session-handoff | +3387 | +| **v0.6.1** | **18** | **frontmatter 关键词化(无 skill 变化)** | **+3391** | + +## v0.6.1 关键变化 + +**只改 frontmatter,不改 skill 本体**: + +```yaml +# 之前 +description: "When a tool call, sub-agent task, or external operation fails, + decide between retry / switch / fallback / ask-user / skip..." + +# 之后 +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). +``` + +**4 段结构**:`USE WHEN` / `TRIGGER PHRASES` / `SKIP WHEN` / 用途 + +**为什么这次**: +- LLM 看到 "ECONNREFUSED" / "permission denied" / "出错了" / "retry" 这些**真实信号**会精确触发 +- 不依赖 LLM 理解抽象描述("when planning complex work" 这种) +- 每次匹配都成功 = skill 真正被用上 + +**没解决的部分**: +- LLM 仍然**不会**主动每 N 步自检"现在该用什么 skill" +- 这要靠 mavis 工具层加 hook API(Level 4)才能彻底解决 +- 在那之前,你(用户)的提醒 + 关键词匹配是兜底 + +## 完整生命周期覆盖(18 skill,v0.6.1) + +``` +规划: plan-stream-emit +拆分: parallel-fanout + fork-context-decision + delegate-with-context +执行: background-task + streaming-output-reader +子 agent: subagent-family-tracking + model-router +质量: review-mode + completion-audit +状态: world-state-tracking + goal-persistence + goal-token-budgeting +容错: error-recovery-strategy + retry-with-backoff +token: tool-output-budget + context-pressure-compact +收尾: session-handoff +``` + +## 当前 18 个 Skill(版本 v0.6.1) + +| # | Skill | v | 灵感来源(Codex) | 分类 | +|---|---|---|---|---| +| 1 | `tool-output-budget` | 0.1.1 | `codex-rs/utils/output-truncation/` | 节省 token | +| 2 | `context-pressure-compact` | 1.0.1 | `codex-rs/core/src/compact.rs` | 节省 token | +| 3 | `parallel-fanout` | 1.0.1 | `core/src/thread_manager.rs` (FuturesUnordered) | 并行 | +| 4 | `plan-stream-emit` | 0.1.1 | `protocol/src/protocol.rs` (PlanUpdate / PlanDelta) | 规划 | +| 5 | `review-mode` | 0.2.1 | `protocol/src/protocol.rs` (EnteredReviewMode) | 质量 | +| 6 | `delegate-with-context` | 1.0.1 | `protocol/src/protocol.rs` (InterAgentCommunication) | 拆解 | +| 7 | `world-state-tracking` | 0.2.1 | `codex-rs/core/src/context/world_state.rs` | 状态 | +| 8 | `background-task` | 0.2.1 | `core/src/unified_exec/` + `CleanBackgroundTerminals` | 效率 | +| 9 | `goal-persistence` | 1.0.1 | `ext/goal/templates/goals/continuation.md` | 状态 | +| 10 | `model-router` | 0.3.1 | `codex-rs/model-provider-info/` + `models-manager/` | 成本 | +| 11 | `completion-audit` | 0.4.1 | `ext/goal/templates/goals/continuation.md` (completion-audit 段) | 质量 | +| 12 | `fork-context-decision` | 0.4.1 | `core/src/session/multi_agents.rs` (fork_turns) | 成本 | +| 13 | `subagent-family-tracking` | 0.5.1 | `agent-graph-store/` | 拆解 | +| 14 | `goal-token-budgeting` | 0.5.1 | `ext/goal/src/accounting.rs` | 状态 | +| 15 | `error-recovery-strategy` | 0.6.1 | `code-mode/src/grpc_session/reconnect.rs` | 容错 | +| 16 | `retry-with-backoff` | 0.6.1 | 同上(retry policy 显式化) | 容错 | +| 17 | `streaming-output-reader` | 0.6.1 | `core/src/client.rs::WebsocketSession` + `unified_exec/` | 效率 | +| 18 | `session-handoff` | 0.6.1 | `state/src/runtime/recovery.rs` | 状态 | + +## 个人仓库 release + +| 版本 | URL | +|---|---| +| v0.6.1 | https://github.com/antianqi/codex-harness-patterns/releases/tag/v0.6.1 | +| v0.6.0 | https://github.com/antianqi/codex-harness-patterns/releases/tag/v0.6.0 | +| v0.5.0 | https://github.com/antianqi/codex-harness-patterns/releases/tag/v0.5.0 | +| v0.4.0 | https://github.com/antianqi/codex-harness-patterns/releases/tag/v0.4.0 | +| v0.3.0 | https://github.com/antianqi/codex-harness-patterns/releases/tag/v0.3.0 | +| v0.2.0 | https://github.com/antianqi/codex-harness-patterns/releases/tag/v0.2.0 | +| v0.1.0 | https://github.com/anianqi/codex-harness-patterns/releases/tag/v0.1.0 | + +## 怎么查 PR 状态 + +```bash +cd C:\Users\Administrator\codex-harness-fork-active +gh pr view 18 +gh pr checks 18 +gh pr view 18 --comments +``` + +## 怎么继续加 skill + +1. 在 `codex-harness-engineering/CATALOG.md` 找下一个 🟢 模式(当前都是 🟢, 48 个) +2. 在 `codex-harness-engineering/knowledge/P-NN-*.md` 写研究笔记 +3. 写 SKILL.md,frontmatter 用 v0.6.1 关键词化格式 +4. 复制到 fork 的 skills/ 目录 +5. 更新 plugin.json / README / CHANGELOG +6. commit + push(自动更新 PR) +7. 同步 standalone 仓库,标新 tag,发 release diff --git a/plugins/antianqi/codex-harness-patterns/README.md b/plugins/antianqi/codex-harness-patterns/README.md index d857dd3..d23ce54 100644 --- a/plugins/antianqi/codex-harness-patterns/README.md +++ b/plugins/antianqi/codex-harness-patterns/README.md @@ -8,7 +8,23 @@ original goal, paying main-model prices for cheap-model work, losing track of wh 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.0 changelog (this release) 🎉 +## v1.0.1 changelog (this release) + +> **类型**: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 + 完整生命周期覆盖 diff --git a/plugins/antianqi/codex-harness-patterns/plugin.json b/plugins/antianqi/codex-harness-patterns/plugin.json index 212bca6..57c1e99 100644 --- a/plugins/antianqi/codex-harness-patterns/plugin.json +++ b/plugins/antianqi/codex-harness-patterns/plugin.json @@ -1,7 +1,7 @@ { "$schema": "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json", "name": "codex-harness-patterns", - "version": "1.0.0", + "version": "1.0.1", "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", From 5b7f1a8cf9b8d2691f2519135f3917e7189cf8e0 Mon Sep 17 00:00:00 2001 From: antianqi Date: Tue, 25 Aug 2026 13:07:13 +0800 Subject: [PATCH 22/49] v1.0.2: README 4-section disclosure (mcode plugin convention) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Per .minimax/memory/user.md plugin 偏好 (2026-08-19, tool-map v0.2): - README 必须有 4 段独立披露: no credentials / no network / no telemetry / no third-party services - 本次 commit 只改 README,不动 23 skills,不动 plugin.json 其他字段 - 无硬编码路径(smoke.mjs 扫描通过) PR #18 body 同步改为 Design compliance / Validation / Test evidence 三段式。 --- .../antianqi/codex-harness-patterns/README.md | 66 ++++++++++++++++++- .../codex-harness-patterns/plugin.json | 2 +- 2 files changed, 66 insertions(+), 2 deletions(-) diff --git a/plugins/antianqi/codex-harness-patterns/README.md b/plugins/antianqi/codex-harness-patterns/README.md index d23ce54..c8c5041 100644 --- a/plugins/antianqi/codex-harness-patterns/README.md +++ b/plugins/antianqi/codex-harness-patterns/README.md @@ -8,7 +8,34 @@ original goal, paying main-model prices for cheap-model work, losing track of wh 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.1 changelog (this release) +## v1.0.2 changelog (this release) + +> **类型**: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 更新) @@ -477,6 +504,43 @@ Eighteen Skills, all Skill-only (no MCP server, no network access): | 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. diff --git a/plugins/antianqi/codex-harness-patterns/plugin.json b/plugins/antianqi/codex-harness-patterns/plugin.json index 57c1e99..6b3efdb 100644 --- a/plugins/antianqi/codex-harness-patterns/plugin.json +++ b/plugins/antianqi/codex-harness-patterns/plugin.json @@ -1,7 +1,7 @@ { "$schema": "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json", "name": "codex-harness-patterns", - "version": "1.0.1", + "version": "1.0.2", "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", From f0c8918aa6cfccb8677493dff53a4e66e187ccb8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=AE=89=E5=A4=A9=E9=BD=90?= Date: Tue, 25 Aug 2026 16:13:44 +0800 Subject: [PATCH 23/49] fix(security): atomicWriteBundle handles all rollback paths The previous implementation only restored target files that had a previous version (backups[name] !== null). Two failure paths were left uncovered: 1. Phase 1 (backup) failure on a later name: any targets already moved to the backup dir were stranded there. The outer catch block cleaned up the backup directory, deleting the old catalog files instead of moving them back. 2. Phase 3 (install) failure: brand-new targets (backups[name] = null) that were already renamed onto the target by an earlier iteration were not cleaned up, leaving a partially-installed new file behind. This rewrite introduces an `installed` tracker alongside `backups` and a single `restore()` function that handles both cases: - For names that had a previous version: move the backup back on top of the new file (or onto the empty target if install never ran). - For names that did not have a previous version: delete the partially-installed new file (or no-op if install never ran). - For names that never made it past Phase 1: restore the backup if one was taken, or no-op if the target was absent. Five new regression tests cover the matrix: - Phase 1 failure on the FIRST name (no backups taken yet). - Phase 1 failure on a LATER name (backups taken for earlier names). - Phase 3 failure after a brand-new target was installed. - Happy path with a previously-empty target dir. - Happy path with a mix of existing and absent targets. Local verification: node --test test/tool-map.test.mjs 17 / 17 PASS (12 original + 5 new) --- plugins/antianqi/tool-map/scripts/scan.mjs | 110 +++++++++----- test-fixtures/drive-bundle-failure-5.mjs | 36 +++++ test/tool-map.test.mjs | 165 +++++++++++++++++++++ 3 files changed, 273 insertions(+), 38 deletions(-) create mode 100644 test-fixtures/drive-bundle-failure-5.mjs diff --git a/plugins/antianqi/tool-map/scripts/scan.mjs b/plugins/antianqi/tool-map/scripts/scan.mjs index d343250..54ab76b 100644 --- a/plugins/antianqi/tool-map/scripts/scan.mjs +++ b/plugins/antianqi/tool-map/scripts/scan.mjs @@ -68,9 +68,12 @@ const outSummary = outMd.replace(/\.md$/, '') + '.summary.md'; // Two-phase commit: every existing target file is first moved to a private // backup directory, then the new contents are written into a staging // directory, then each staging file is renamed onto its target. If any -// rename fails, the backups are restored and the staging dir is removed. -// Net effect: the previous catalog is left completely untouched unless -// every file in the bundle renames successfully. +// step fails, the previous bundle is restored exactly: names that had a +// target get their old contents back, and names that did NOT have a +// target are left absent (any partially-installed new content is removed). +// Net effect: after a failure, the target directory looks identical to its +// pre-call state. The previous catalog is left completely untouched +// unless every file in the bundle renames successfully. // // On POSIX `rename(2)` is atomic. On Windows `fs.renameSync` calls // `MoveFileExW` with `MOVEFILE_REPLACE_EXISTING`; same-volume moves are @@ -88,54 +91,85 @@ function atomicWriteBundle(targetDir, files) { mkdirSync(stagingDir, { recursive: true }); mkdirSync(backupDir, { recursive: true }); - // Phase 1: back up any existing target files. Track which names had a - // previous version so we know whether to remove the backup or restore it. - const backups = {}; // name -> backup path (or null if target didn't exist) - for (const name of Object.keys(files)) { - const targetPath = join(targetDir, name); - if (existsSync(targetPath)) { - const backupPath = join(backupDir, name); - renameSync(targetPath, backupPath); - backups[name] = backupPath; - } else { - backups[name] = null; + // Per-name state tracked across the three phases. Both start empty. + // backups[name] - string path: the target existed and was moved to + // this backup path in Phase 1. + // - null: the target did NOT exist before Phase 1. + // installed[name] - true: Phase 3 has already renamed the new file + // onto the target. Used to know whether a brand-new + // file needs to be deleted on rollback. + const backups = {}; + const installed = {}; + + // Inverse of Phases 1+3: put every name back into the state it was in + // before this call. Handles both "target had a previous version" + // (restore from backup) and "target was absent" (delete the partially + // installed new file). Best-effort: any individual rename/rm failure + // is swallowed so the outer error can still surface. + const restore = () => { + for (const [name, backupPath] of Object.entries(backups)) { + const targetPath = join(targetDir, name); + if (installed[name]) { + // A new file is sitting on the target right now. Either move the + // backup back on top of it (old contents win) or, if there was + // no previous file, delete the new one. + if (backupPath) { + try { renameSync(backupPath, targetPath); } catch { /* best effort */ } + } else { + try { rmSync(targetPath, { force: true }); } catch { /* best effort */ } + } + } else if (backupPath) { + // Phase 1 moved the old file to backup but Phase 3 hasn't run for + // this name yet (or, for failure during Phase 1 itself, the loop + // broke before reaching this name). Move the old file back. + try { renameSync(backupPath, targetPath); } catch { /* best effort */ } + } + // else: target was absent and is still absent - nothing to do. } + }; + + // Phase 1: back up any existing target files. If a backup rename fails, + // any names already backed up must be moved back to their targets so + // the caller sees the same directory state as before this call. + try { + for (const name of Object.keys(files)) { + const targetPath = join(targetDir, name); + if (existsSync(targetPath)) { + const backupPath = join(backupDir, name); + renameSync(targetPath, backupPath); + backups[name] = backupPath; + } else { + backups[name] = null; + } + } + } catch (err) { + restore(); + try { rmSync(backupDir, { recursive: true, force: true }); } catch { /* swallow */ } + try { rmSync(stagingDir, { recursive: true, force: true }); } catch { /* swallow */ } + throw err; } + // Phase 2 + 3: write all new content into the staging dir, then rename + // each onto its target. Track which names have actually been installed + // so the rollback path can clean up brand-new files too. try { - // Phase 2: write all new content into the staging dir. for (const [name, contents] of Object.entries(files)) { writeFileSync(join(stagingDir, name), contents, 'utf8'); } - - // Phase 3: rename each staging file onto its target. If any rename - // fails, restore the previous targets from backup before throwing. - try { - for (const name of Object.keys(files)) { - renameSync(join(stagingDir, name), join(targetDir, name)); - } - } catch (renameErr) { - // Restore backups (target paths are now empty or partially written) - for (const [name, backupPath] of Object.entries(backups)) { - if (backupPath) { - try { renameSync(backupPath, join(targetDir, name)); } catch { /* best effort */ } - } - } - // Re-throw after restoring - throw renameErr; + for (const name of Object.keys(files)) { + renameSync(join(stagingDir, name), join(targetDir, name)); + installed[name] = true; } - - // Phase 4: success. Remove the backup and staging directories. - try { rmSync(backupDir, { recursive: true, force: true }); } catch { /* swallow */ } - try { rmSync(stagingDir, { recursive: true, force: true }); } catch { /* swallow */ } } catch (err) { - // Any failure inside Phase 2 (write) or 3 (rename): also restore backups - // and clean up both staging and backup dirs. Phase 3 already restores - // backups in its catch above, so we only need to clean up here. + restore(); try { rmSync(backupDir, { recursive: true, force: true }); } catch { /* swallow */ } try { rmSync(stagingDir, { recursive: true, force: true }); } catch { /* swallow */ } throw err; } + + // Phase 4: success. Remove the backup and staging directories. + try { rmSync(backupDir, { recursive: true, force: true }); } catch { /* swallow */ } + try { rmSync(stagingDir, { recursive: true, force: true }); } catch { /* swallow */ } } // --- Scan config --- diff --git a/test-fixtures/drive-bundle-failure-5.mjs b/test-fixtures/drive-bundle-failure-5.mjs new file mode 100644 index 0000000..d462f4a --- /dev/null +++ b/test-fixtures/drive-bundle-failure-5.mjs @@ -0,0 +1,36 @@ +// Test helper: drive atomicWriteBundle with FIVE files instead of three +// (the original drive-bundle-failure.mjs uses three, which is fine for +// the Phase-3 mid-bundle test that lands failure on rename #4). The +// brand-new partial-install test needs renames #1-#8 to be triggered +// across Phases 1+3, with failure on #8, so the three-file helper is +// not enough. +// +// Renames driven: #1=md-backup, #2=json-backup, #3=summary-backup +// (no backup for new1, new2) +// #4=md-install, #5=json-install, #6=summary-install, +// #7=new1-install, #8=new2-install +// Triggering TOOL_MAP_FAIL_AT_RENAME=8 causes new2-install to throw +// after new1 has already been renamed onto its (previously-absent) target. +// +// Usage: node test-fixtures/drive-bundle-failure-5.mjs +// Exits 0 on unexpected success, non-zero on expected throw. + +import { pathToFileURL } from 'node:url'; +import { resolve } from 'node:path'; + +const target = resolve(process.argv[2]); +const scanUrl = pathToFileURL( + resolve(process.argv[1], '..', '..', 'plugins', 'antianqi', 'tool-map', 'scripts', 'scan.mjs'), +).href; + +const { atomicWriteBundle } = await import(scanUrl); + +atomicWriteBundle(target, { + 'tools.md': 'NEW-MD', + 'tools.json': 'NEW-JSON', + 'tools.summary.md': 'NEW-SUMMARY', + 'tools.new1': 'NEW-NEW1', + 'tools.new2': 'NEW-NEW2', +}); +console.log('UNEXPECTED success'); +process.exit(99); diff --git a/test/tool-map.test.mjs b/test/tool-map.test.mjs index f2fc9c5..367de1a 100644 --- a/test/tool-map.test.mjs +++ b/test/tool-map.test.mjs @@ -211,6 +211,171 @@ test('atomicWriteBundle is idempotent on the happy path (no residue, all 3 prese } }); +test('atomicWriteBundle rolls back when a backup-phase rename fails (early name)', async () => { + // Phase 1 (backup) failure on the very first name. `backups` is still + // empty, so the only thing the rollback must do is clean up the empty + // staging and backup dirs and leave the targets untouched. This is the + // simplest backup-phase case: no previous files have been moved yet. + const work = mkdtempSync(join(tmpdir(), 'tool-map-bkp-early-')); + try { + writeFileSync(join(work, 'tools.md'), 'PRE-MD'); + writeFileSync(join(work, 'tools.json'), 'PRE-JSON'); + // No tools.summary.md - target was absent before this call. + + const helperPath = join(REPO_ROOT, 'test-fixtures', 'drive-bundle-failure.mjs'); + const r = spawnSync(process.execPath, [helperPath, work], { + encoding: 'utf8', + timeout: 15_000, + env: { ...process.env, TOOL_MAP_FAIL_AT_RENAME: '1' }, + }); + assert.notEqual(r.status, 0, `helper should exit non-zero when the hook fires: stdout=${r.stdout}\nstderr=${r.stderr}`); + + // Previous targets are intact. + assert.equal(readFileSync(join(work, 'tools.md'), 'utf8'), 'PRE-MD'); + assert.equal(readFileSync(join(work, 'tools.json'), 'utf8'), 'PRE-JSON'); + // The previously-absent target is still absent. + assert.ok(!existsSync(join(work, 'tools.summary.md')), 'tools.summary.md was created on rollback'); + // No residue. + const residue = readdirSync(work).filter((e) => + e.includes('.staging-') || e.includes('.bundle.staging-') || e.includes('.bundle.backup-'), + ); + assert.equal(residue.length, 0, `staging/backup residue after Phase-1 rollback: ${residue.join(', ')}`); + } finally { + rmSync(work, { recursive: true, force: true }); + } +}); + +test('atomicWriteBundle rolls back when a backup-phase rename fails (later name)', async () => { + // Phase 1 (backup) failure on the SECOND name. tools.md has already + // been moved to the backup dir; a naive implementation would leave it + // stranded there. The rollback must move it back to its target. + const work = mkdtempSync(join(tmpdir(), 'tool-map-bkp-late-')); + try { + writeFileSync(join(work, 'tools.md'), 'PRE-MD'); + writeFileSync(join(work, 'tools.json'), 'PRE-JSON'); + writeFileSync(join(work, 'tools.summary.md'), 'PRE-SUMMARY'); + + const helperPath = join(REPO_ROOT, 'test-fixtures', 'drive-bundle-failure.mjs'); + const r = spawnSync(process.execPath, [helperPath, work], { + encoding: 'utf8', + timeout: 15_000, + env: { ...process.env, TOOL_MAP_FAIL_AT_RENAME: '2' }, + }); + assert.notEqual(r.status, 0, `helper should exit non-zero when the hook fires: stdout=${r.stdout}\nstderr=${r.stderr}`); + + // Every previous target is back in place, byte-for-byte. + assert.equal(readFileSync(join(work, 'tools.md'), 'utf8'), 'PRE-MD', + 'tools.md was stranded in backup dir instead of restored to target'); + assert.equal(readFileSync(join(work, 'tools.json'), 'utf8'), 'PRE-JSON'); + assert.equal(readFileSync(join(work, 'tools.summary.md'), 'utf8'), 'PRE-SUMMARY'); + // No residue. + const residue = readdirSync(work).filter((e) => + e.includes('.staging-') || e.includes('.bundle.staging-') || e.includes('.bundle.backup-'), + ); + assert.equal(residue.length, 0, `staging/backup residue after Phase-1 rollback: ${residue.join(', ')}`); + } finally { + rmSync(work, { recursive: true, force: true }); + } +}); + +test('atomicWriteBundle rolls back brand-new files that were partially installed', async () => { + // Phase 3 (install) failure on the LAST name after a brand-new file + // (one that did NOT exist before this call) was successfully installed. + // The rollback must delete the partially-installed new file so the + // directory looks like it did before the call. + // + // Renames: #1=md-backup, #2=json-backup, #3=summary-backup + // (no backup for tools.new1) + // #4=md-install, #5=json-install, #6=summary-install, + // #7=new1-install, #8=new2-install + // Trigger at #8 so the failure happens after the brand-new tools.new1 + // has already been renamed onto its target. `installed['tools.new1']` + // is true and `backups['tools.new1']` is null. + const work = mkdtempSync(join(tmpdir(), 'tool-map-new-partial-')); + try { + writeFileSync(join(work, 'tools.md'), 'PRE-MD'); + writeFileSync(join(work, 'tools.json'), 'PRE-JSON'); + writeFileSync(join(work, 'tools.summary.md'), 'PRE-SUMMARY'); + // tools.new1 and tools.new2 do NOT exist before the call. + + const helperUrl = pathToFileURL(join(REPO_ROOT, 'test-fixtures', 'drive-bundle-failure-5.mjs')).href; + const r = spawnSync(process.execPath, [helperUrl, work], { + encoding: 'utf8', + timeout: 15_000, + env: { ...process.env, TOOL_MAP_FAIL_AT_RENAME: '8' }, + }); + assert.notEqual(r.status, 0, `helper should exit non-zero when the hook fires: stdout=${r.stdout}\nstderr=${r.stderr}`); + + // Previously-existing targets are restored. + assert.equal(readFileSync(join(work, 'tools.md'), 'utf8'), 'PRE-MD'); + assert.equal(readFileSync(join(work, 'tools.json'), 'utf8'), 'PRE-JSON'); + assert.equal(readFileSync(join(work, 'tools.summary.md'), 'utf8'), 'PRE-SUMMARY'); + // Brand-new targets are still absent (no residue from partial install). + assert.ok(!existsSync(join(work, 'tools.new1')), 'brand-new tools.new1 leaked after rollback'); + assert.ok(!existsSync(join(work, 'tools.new2')), 'brand-new tools.new2 leaked after rollback'); + // No residue. + const residue = readdirSync(work).filter((e) => + e.includes('.staging-') || e.includes('.bundle.staging-') || e.includes('.bundle.backup-'), + ); + assert.equal(residue.length, 0, `staging/backup residue after Phase-3 rollback: ${residue.join(', ')}`); + } finally { + rmSync(work, { recursive: true, force: true }); + } +}); + +test('atomicWriteBundle happy path: previously-absent targets are created, no residue', async () => { + // When the target dir starts empty, every name in the bundle is a + // brand-new file. The happy path must still leave exactly the three + // target files behind and nothing else. + const scanUrl = pathToFileURL(SCAN).href; + const { atomicWriteBundle } = await import(scanUrl); + const work = mkdtempSync(join(tmpdir(), 'tool-map-fresh-')); + try { + atomicWriteBundle(work, { + 'tools.md': 'NEW-MD', + 'tools.json': 'NEW-JSON', + 'tools.summary.md': 'NEW-SUMMARY', + }); + assert.equal(readFileSync(join(work, 'tools.md'), 'utf8'), 'NEW-MD'); + assert.equal(readFileSync(join(work, 'tools.json'), 'utf8'), 'NEW-JSON'); + assert.equal(readFileSync(join(work, 'tools.summary.md'), 'utf8'), 'NEW-SUMMARY'); + const entries = readdirSync(work).sort(); + assert.deepEqual(entries, ['tools.json', 'tools.md', 'tools.summary.md'], + `unexpected files in fresh output dir: ${entries.join(', ')}`); + } finally { + rmSync(work, { recursive: true, force: true }); + } +}); + +test('atomicWriteBundle happy path: mix of existing and absent targets', async () => { + // Verify the happy path still works when only SOME of the targets + // pre-existed. The existing ones get overwritten, the absent ones get + // created, no residue anywhere. + const scanUrl = pathToFileURL(SCAN).href; + const { atomicWriteBundle } = await import(scanUrl); + const work = mkdtempSync(join(tmpdir(), 'tool-map-mixed-')); + try { + writeFileSync(join(work, 'tools.md'), 'OLD-MD'); + writeFileSync(join(work, 'tools.json'), 'OLD-JSON'); + // tools.summary.md is absent. + + atomicWriteBundle(work, { + 'tools.md': 'NEW-MD', + 'tools.json': 'NEW-JSON', + 'tools.summary.md': 'NEW-SUMMARY', + }); + assert.equal(readFileSync(join(work, 'tools.md'), 'utf8'), 'NEW-MD'); + assert.equal(readFileSync(join(work, 'tools.json'), 'utf8'), 'NEW-JSON'); + assert.equal(readFileSync(join(work, 'tools.summary.md'), 'utf8'), 'NEW-SUMMARY'); + const residue = readdirSync(work).filter((e) => + e.includes('.staging-') || e.includes('.bundle.staging-') || e.includes('.bundle.backup-'), + ); + assert.equal(residue.length, 0, `staging/backup residue on happy path: ${residue.join(', ')}`); + } finally { + rmSync(work, { recursive: true, force: true }); + } +}); + test('ALLOWED_PROBE_NAMES is exactly the 15 declared names', async () => { const scanUrl = pathToFileURL(SCAN).href; const { ALLOWED_PROBE_NAMES, VERSION_PROBES } = await import(scanUrl); From 1f4530c2a66a1f5a5c94db9020cbfb9eb7f411a6 Mon Sep 17 00:00:00 2001 From: antianqi Date: Tue, 25 Aug 2026 17:11:21 +0800 Subject: [PATCH 24/49] fix: remove Codex-only tool params from 5 Skills (reviewer feedback) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Resolves the first half of the hetaoBackend CHANGES_REQUESTED review (https://github.com/MiniMax-AI/MiniMax-Code-Plugins/pull/18#pullrequestreview-...). Affected Skills and the Codex-only params that were removed: - fork-context-decision: fork_turns=N -> pseudocode + 'mcode 适配' note - parallel-fanout: subagent=..., fork_turns=N -> pseudocode + note - delegate-with-context: subagent=..., task_name=..., fork_turns=N -> envelope only - background-task: task_name=..., run_in_background=..., action='kill' -> pseudocode + note - model-router: model_config_id=anthropic-sonnet-4 with reasoning_effort=high -> portable 3-tier rubric + note Each affected Skill now: 1. Teaches the DESIGN DECISION (what context, what tier, what handle) 2. Marks example calls as Codex-harness-style PSEUDOCODE 3. Adds an explicit 'mcode 适配' section telling the agent to adapt parameter names to the actual host API The Skills no longer prescribe invalid tool calls that mcode cannot execute. Reviewer point 2 is partially addressed. Also rewrites PR-STATUS.md to match v1.0.2 / 23 Skills inventory (reviewer point 1). Test evidence: - 5 SKILL.md updated - 0 new tool invocations invented - 0 hard-coded paths introduced --- .../codex-harness-patterns/PR-STATUS.md | 165 +++------- .../skills/background-task/SKILL.md | 180 +++++------ .../skills/delegate-with-context/SKILL.md | 282 ++++++------------ .../skills/fork-context-decision/SKILL.md | 176 +++++------ .../skills/model-router/SKILL.md | 19 +- .../skills/parallel-fanout/SKILL.md | 253 ++++++---------- 6 files changed, 410 insertions(+), 665 deletions(-) diff --git a/plugins/antianqi/codex-harness-patterns/PR-STATUS.md b/plugins/antianqi/codex-harness-patterns/PR-STATUS.md index b5554df..53fdd47 100644 --- a/plugins/antianqi/codex-harness-patterns/PR-STATUS.md +++ b/plugins/antianqi/codex-harness-patterns/PR-STATUS.md @@ -1,6 +1,6 @@ # PR 状态 -> 最后更新:2026-08-24 +> 最后更新:2026-08-25 ## 当前状态 @@ -10,129 +10,40 @@ | **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 | -| **当前版本** | v0.6.1 (patch — frontmatter only) | -| **前一个版本** | v0.6.0 | -| **变更** | +3391 / -0 行,21 个文件 | -| **本地 CI** | ✅ CI: success | -| **第三方 bot** | [code]smith: SKIPPED | -| **安全扫描** | ⏳ CodeQL: 跑过前两次,需要再确认最新 | -| **官方 review** | ⏳ 等维护者 | - -## 版本演进 - -| 版本 | Skills | 主要内容 | 累计 PR 变更 | -|---|---|---|---| -| v0.1.0 | 4 | tool-output-budget / context-pressure-compact / parallel-fanout / plan-stream-emit | +836 | -| v0.2.0 | 8 | + review-mode / delegate-with-context / world-state-tracking / background-task | +1451 | -| v0.3.0 | 10 | + goal-persistence / model-router | +2272 | -| v0.4.0 | 12 | + completion-audit / fork-context-decision;goal-persistence + parallel-fanout 升 v1.0 | +2799 | -| v0.5.0 | 14 | + subagent-family-tracking / goal-token-budgeting;context-pressure-compact + delegate-with-context 升 v1.0 | +3387 | -| v0.6.0 | 18 | + error-recovery / retry / streaming / session-handoff | +3387 | -| **v0.6.1** | **18** | **frontmatter 关键词化(无 skill 变化)** | **+3391** | - -## v0.6.1 关键变化 - -**只改 frontmatter,不改 skill 本体**: - -```yaml -# 之前 -description: "When a tool call, sub-agent task, or external operation fails, - decide between retry / switch / fallback / ask-user / skip..." - -# 之后 -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). -``` - -**4 段结构**:`USE WHEN` / `TRIGGER PHRASES` / `SKIP WHEN` / 用途 - -**为什么这次**: -- LLM 看到 "ECONNREFUSED" / "permission denied" / "出错了" / "retry" 这些**真实信号**会精确触发 -- 不依赖 LLM 理解抽象描述("when planning complex work" 这种) -- 每次匹配都成功 = skill 真正被用上 - -**没解决的部分**: -- LLM 仍然**不会**主动每 N 步自检"现在该用什么 skill" -- 这要靠 mavis 工具层加 hook API(Level 4)才能彻底解决 -- 在那之前,你(用户)的提醒 + 关键词匹配是兜底 - -## 完整生命周期覆盖(18 skill,v0.6.1) - -``` -规划: plan-stream-emit -拆分: parallel-fanout + fork-context-decision + delegate-with-context -执行: background-task + streaming-output-reader -子 agent: subagent-family-tracking + model-router -质量: review-mode + completion-audit -状态: world-state-tracking + goal-persistence + goal-token-budgeting -容错: error-recovery-strategy + retry-with-backoff -token: tool-output-budget + context-pressure-compact -收尾: session-handoff -``` - -## 当前 18 个 Skill(版本 v0.6.1) - -| # | Skill | v | 灵感来源(Codex) | 分类 | -|---|---|---|---|---| -| 1 | `tool-output-budget` | 0.1.1 | `codex-rs/utils/output-truncation/` | 节省 token | -| 2 | `context-pressure-compact` | 1.0.1 | `codex-rs/core/src/compact.rs` | 节省 token | -| 3 | `parallel-fanout` | 1.0.1 | `core/src/thread_manager.rs` (FuturesUnordered) | 并行 | -| 4 | `plan-stream-emit` | 0.1.1 | `protocol/src/protocol.rs` (PlanUpdate / PlanDelta) | 规划 | -| 5 | `review-mode` | 0.2.1 | `protocol/src/protocol.rs` (EnteredReviewMode) | 质量 | -| 6 | `delegate-with-context` | 1.0.1 | `protocol/src/protocol.rs` (InterAgentCommunication) | 拆解 | -| 7 | `world-state-tracking` | 0.2.1 | `codex-rs/core/src/context/world_state.rs` | 状态 | -| 8 | `background-task` | 0.2.1 | `core/src/unified_exec/` + `CleanBackgroundTerminals` | 效率 | -| 9 | `goal-persistence` | 1.0.1 | `ext/goal/templates/goals/continuation.md` | 状态 | -| 10 | `model-router` | 0.3.1 | `codex-rs/model-provider-info/` + `models-manager/` | 成本 | -| 11 | `completion-audit` | 0.4.1 | `ext/goal/templates/goals/continuation.md` (completion-audit 段) | 质量 | -| 12 | `fork-context-decision` | 0.4.1 | `core/src/session/multi_agents.rs` (fork_turns) | 成本 | -| 13 | `subagent-family-tracking` | 0.5.1 | `agent-graph-store/` | 拆解 | -| 14 | `goal-token-budgeting` | 0.5.1 | `ext/goal/src/accounting.rs` | 状态 | -| 15 | `error-recovery-strategy` | 0.6.1 | `code-mode/src/grpc_session/reconnect.rs` | 容错 | -| 16 | `retry-with-backoff` | 0.6.1 | 同上(retry policy 显式化) | 容错 | -| 17 | `streaming-output-reader` | 0.6.1 | `core/src/client.rs::WebsocketSession` + `unified_exec/` | 效率 | -| 18 | `session-handoff` | 0.6.1 | `state/src/runtime/recovery.rs` | 状态 | - -## 个人仓库 release - -| 版本 | URL | -|---|---| -| v0.6.1 | https://github.com/antianqi/codex-harness-patterns/releases/tag/v0.6.1 | -| v0.6.0 | https://github.com/antianqi/codex-harness-patterns/releases/tag/v0.6.0 | -| v0.5.0 | https://github.com/antianqi/codex-harness-patterns/releases/tag/v0.5.0 | -| v0.4.0 | https://github.com/antianqi/codex-harness-patterns/releases/tag/v0.4.0 | -| v0.3.0 | https://github.com/antianqi/codex-harness-patterns/releases/tag/v0.3.0 | -| v0.2.0 | https://github.com/antianqi/codex-harness-patterns/releases/tag/v0.2.0 | -| v0.1.0 | https://github.com/anianqi/codex-harness-patterns/releases/tag/v0.1.0 | - -## 怎么查 PR 状态 - -```bash -cd C:\Users\Administrator\codex-harness-fork-active -gh pr view 18 -gh pr checks 18 -gh pr view 18 --comments -``` - -## 怎么继续加 skill - -1. 在 `codex-harness-engineering/CATALOG.md` 找下一个 🟢 模式(当前都是 🟢, 48 个) -2. 在 `codex-harness-engineering/knowledge/P-NN-*.md` 写研究笔记 -3. 写 SKILL.md,frontmatter 用 v0.6.1 关键词化格式 -4. 复制到 fork 的 skills/ 目录 -5. 更新 plugin.json / README / CHANGELOG -6. commit + push(自动更新 PR) -7. 同步 standalone 仓库,标新 tag,发 release +| **状态** | OPEN — review: CHANGES_REQUESTED by hetaoBackend | +| **当前版本** | v1.0.2 (patch: README 4-section disclosure) | +| **前一版本** | v1.0.1 (documentation refresh) | +| **Plugin size** | 23 Skills(在 64 上限内)+ 1 manifest + 1 README + 1 LICENSE | +| **变更** | +3xxx / -xxx 行,28 文件 | +| **静态 CI** | ⚠️ [code]smith: SKIPPED | +| **CodeQL** | 待扫 | +| **官方 review** | ⚠️ hetaoBackend (COLLABORATOR): 3 个 CHANGES_REQUESTED issues,正在修复 | + +## 已知 reviewer issues(2026-08-25 收到) + +来自 hetaoBackend 评审,3 个 blocking 问题: + +### Issue 1 · 文档版本不一致 +- **现状**:v1.0.2 manifest + OVERVIEW 跟历史 PR-STATUS.md / README changelog 不一致 +- **修复**:本文件已重写,统一为 v1.0.2 / 23 Skills + +### Issue 2 · Codex-only 工具参数 +- **现状**:5 个 Skill 用了 mcode 不存在的 Codex 工具参数: + - `fork_turns`(fork-context-decision, parallel-fanout, delegate-with-context) + - `task_name`(background-task, delegate-with-context) + - `bash(action="kill")`(background-task) + - `subagent=...`(parallel-fanout, delegate-with-context) + - `reasoning_effort`(model-router) +- **修复**:SKILL.md 重写,移除 Codex-only 参数,标注为 "Codex 习惯 + mcode 工具的等价为 ..." 注释,example 改为 mcode 实际工具调用形式 + +### Issue 3 · plugin-authoring / memory 写行为未声明 host 边界 +- **现状**:`plugin-author-helper` 和 `long-term-memory` 描述了网络/安装/写文件行为,未声明需 user 确认 +- **修复**:每个描述副作用的章节加 "需要 user 确认 / 需要 plugin runtime 支持" 前缀 + +## 修复计划(commit 拆分) + +1. **commit 1**: 修 PR-STATUS.md / README changelog(版本统一)— 立即 +2. **commit 2**: 修 5 个 SKILL.md,移除 Codex-only 参数 — 立即 +3. **commit 3**: 修 plugin-author-helper + long-term-memory,加 user-confirmation 边界 — 立即 + +每个 commit 一个 fix,不掺其他改动。 diff --git a/plugins/antianqi/codex-harness-patterns/skills/background-task/SKILL.md b/plugins/antianqi/codex-harness-patterns/skills/background-task/SKILL.md index 5a00037..1353b60 100644 --- a/plugins/antianqi/codex-harness-patterns/skills/background-task/SKILL.md +++ b/plugins/antianqi/codex-harness-patterns/skills/background-task/SKILL.md @@ -1,121 +1,129 @@ --- name: background-task description: | - Run long-running command as background task instead of blocking conversation. - USE WHEN: command expected > 30s, dev server / build / watcher / test loop / `tail -f` / long npm/cargo/make output, user said "in the background" / "don't block" / "后台" / "并行跑" / "kick off", earlier foreground call timed out, want to keep talking while command runs, file sync / `fswatch` / live-reload. - TRIGGER PHRASES: "后台", "background", "in the background", "并行跑", "don't block", "继续做别的事", "kick off the build", "start the server", "跑着不用等", "background task", "起个 server", "watch 一下". - SKIP WHEN: command is short (<30s), output is the deliverable (read in one shot), destructive command needing exit code. + Decide when to put a long-running shell command in the background and how to refer to it later. + USE WHEN: a 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 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: Requires MiniMax Code with Agent Plugins 1.0 support. +compatibility: Requires MiniMax Code with Agent Plugins 1.0 support. The example calls in this Skill are written in Codex-harness style (pseudocode) using `bash(task_name=..., run_in_background=true)`; MiniMax Code's tool surface may not expose these exact parameter names — adapt the call to the actual host API. metadata: author: antianqi - version: "0.1.1" - inspired-by: https://github.com/openai/codex/blob/main/codex-rs/core/src/unified_exec/ + version: "0.1.2" + inspired-by: https://github.com/openai/codex/blob/main/codex-rs/core/src/unified_exec/ and protocol::Op::CleanBackgroundTerminals (design principle only; the example parameter names are Codex-specific) + changes-from-v0.1.1: "Rewritten to be host-agnostic. The example calls are now pseudocode with an explicit mcode adaptation note. Removed `bash(action=\"kill\")` (mcode has no such sub-action); replaced with a generic 'stop it via the host's job-control API'." --- # Background Task -Run a long-running command as a background task and return control to the agent immediately. -Poll, steer, or kill it on later turns. Do not block the conversation on a 10-minute build. +When a shell command is expected to take more than ~30 seconds, the agent has two choices: + +1. **Block**: wait for the command 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 适配**:本 Skill 的 example 调用用 Codex-harness 风格(`bash(task_name=..., +> run_in_background=true, action="kill")`)作为**伪代码**。MiniMax Code 当前 +> host 工具的 `bash` 调用**不暴露** `task_name` / `run_in_background` / +> `action="kill"` 这些 sub-action 参数。请**根据实际 host 工具改写**(例如: +> 用 `Start-Process` / `nohup` / 系统的 job control 启动,然后在另一个 turn 重新 +> 调 `bash` 探查)。**不要把伪代码当真实调用复制**。 ## When to use Activate when **any** of these is true: -- The command is expected to take > 30 seconds (a full `cargo test`, a `vite dev` server, a - `webpack --watch`). -- The user has said "start the dev server in the background" / "kick off the build" / "let - me know when it's done". -- You need to run multiple long commands and want them to overlap. -- A previous foreground call already hit a timeout. -- The command is meant to run indefinitely (`watch`, `serve`, `tail -f`) and you only need - to read its output on demand. +- A 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 command is short (< 30 seconds). Just run it in the foreground. -- The command's output is the entire point (a `curl` whose body you must inspect). Read it - in one shot, not as a stream. -- The command is destructive and you need to see the result before continuing (e.g. - `rm -rf`). Run it foreground, see the exit code, then decide. +- The 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. **State the start plan** in one line before launching: "Starting `npm run dev` in the - background (expected ~5s to be ready, polling every 10s)." -2. **Launch with `run_in_background: true`** (or your harness's equivalent). Pick a - descriptive `task_name` so the user can recognise it: `dev-server`, `cargo-test`, - `vite-watch`. Not `task1`. -3. **On launch, do not block.** Return immediately to whatever the user asked next. Do - not poll in the same turn unless the user explicitly asked you to wait. -4. **On a later turn (or when the user asks "is it ready?"):** - - `read` the output buffer (or `tail` the log file if you wrote one). - - If still running, report progress and continue. - - If exited, report the exit code and a one-line summary of the last output. -5. **On user request to stop** (or when the task is no longer needed): kill the background - task. Confirm with the user before killing anything they explicitly started. -6. **At end of session / on `context-pressure-compact`:** list the running background tasks - in the state file so they survive the compaction. +1. **Estimate the duration**. If unsure, assume the worst case (> 30s). If a 2-second + result is fine, just run it blocking. +2. **Choose a descriptive handle**. The agent (and the user) will need to recognise + it later in the conversation. `dev-server` is good. `task1` is bad. +3. **Launch the background process using the host's job-control mechanism**: + - Codex-harness style (pseudocode, adapt to your host): + `bash(task_name="dev-server", run_in_background=true, command="npm run dev")` + - MiniMax Code style (use whatever the host actually supports; e.g. `Start-Process` + on Windows, `nohup` or `&` + `disown` on POSIX, or simply record the PID and + re-`bash` against it on a later turn). +4. **Record the handle**. In a multi-step task, store the handle (PID, name, 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 process. +6. **When the result matters**, check on the process. Read its log, poll its status, + or kill it if it is no longer needed (using the host's stop API, **not** a + `bash(action="kill")` that does not exist on MiniMax Code). ## Output contract -The user sees, in this order: +After activating this Skill, the agent's next message MUST include: + +- The chosen **task name / handle**. +- 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 -- One-line "starting X in background" plan. -- The launch invocation (one line, with the `task_name`). -- A short status line on every later turn that touches the task: "X: running, 3124 lines - of output so far" / "X: exited 0, last line '...'" / "X: still running, no output yet". -- A clean "stopped X" when killed. +- **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. +- **Assuming the host supports `task_name` / `run_in_background` / `action="kill"`** — + those are Codex-specific parameter names. MiniMax Code's `bash` tool may not + expose them. Use the host's actual job-control mechanism. ## Example -```text -> bash(task_name="cargo-test", run_in_background=true, - prompt="cd /repo && cargo test --workspace 2>&1 | tee /tmp/cargo-test.log") -launched cargo-test (id: bt-7a3f); returning to user - -[user asks "how's the test run?" two minutes later] - -> read offset=0 limit=200 /tmp/cargo-test.log -cargo-test: running, 1234 lines of output so far - ✓ 23 passed in 0.4s - ✓ 7 passed in 0.2s - … - running 12 of 240 tests (auth::session::rotate) - no failures yet -``` +The example below is **Codex-harness style pseudocode** for clarity. On MiniMax Code, +the `bash` tool's parameter names for background execution are **not exposed**; +adapt the call to whatever the host actually supports (e.g. `Start-Process`, +`nohup &`, PID polling, etc.). ```text -[user asks "stop the test run, I want to fix the failing one manually"] - -> bash(task_name="cargo-test", action="kill") -stopped cargo-test; last output preserved at /tmp/cargo-test.log +# Codex-harness style (pseudocode for design clarity): +> bash( + task_name="dev-server", + run_in_background=true, + command="npm run dev" + ) + +# MiniMax Code style (fill in the real host API): +# Option A: launch detached, then poll +$proc = Start-Process -FilePath "npm" -ArgumentList "run","dev" -PassThru -NoNewWindow +# record $proc.Id somewhere +# later: Get-Process -Id $proc.Id | ... + +# Option B: simply run blocking, then do the next thing in the SAME turn +# (the agent's host runs them sequentially anyway) ``` -## Common pitfalls - -- **Do not launch with `run_in_background: true` and then immediately poll in the same - turn.** That defeats the purpose. Launch and return; poll on a later turn or when the - user asks. -- **Do not use generic `task_name` values.** `dev-server` is good, `task1` is bad — the - user will not know which task is which after the second background task. -- **Do not buffer the entire output in the context.** If the task writes to a log file, - `read` with `offset` + `limit` or `tail`. Do not `cat` the whole thing. -- **Do not assume the task is healthy just because it is running.** A 5-minute `cargo test` - with no new output is hung, not progressing. Check the log. -- **Do not forget to kill.** Background tasks that the user no longer needs are silent - resource leaks. List them in the state file and clean up on session end. -- **Do not start a background task that writes to stdout the agent must read in real time.** - Use a log file. Stdout from a backgrounded process is awkward to recover reliably. -- **Do not block the conversation on the task's first output.** The first output is often - not informative (build setup, server starting, test warming up). +The **decision** (background, with a recorded handle) is the same; the **execution +mechanism** depends on the host. ## Verification checklist -- [ ] Did you state the start plan in one line? -- [ ] Did you use `run_in_background: true` (or equivalent) and a descriptive `task_name`? -- [ ] Did you return to the user immediately, not block on the first output? -- [ ] Is the task's output going to a log file (so polling is cheap)? -- [ ] On later turns, is the status report one line with exit code + last line of output? -- [ ] On stop, did you confirm with the user before killing? -- [ ] Are running background tasks listed in the state file for compaction survival? +- [ ] Did you estimate the duration before choosing background vs blocking? +- [ ] Did you choose a **descriptive** name (not `task1`)? +- [ ] Did you record the handle (PID / log path) in a persistent place? +- [ ] Did you tell the user "I launched X in the background, here's the log path"? +- [ ] Did you avoid using Codex-only `bash` parameter names verbatim? 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 index 0e0e14e..edca7ee 100644 --- a/plugins/antianqi/codex-harness-patterns/skills/delegate-with-context/SKILL.md +++ b/plugins/antianqi/codex-harness-patterns/skills/delegate-with-context/SKILL.md @@ -1,229 +1,135 @@ --- name: delegate-with-context description: | - Write a minimal-context brief for `task()` instead of dumping full history. - USE WHEN: about to call `task()` to hand off sub-task, full conversation history > 30 turns, sub-task has clear boundary, find yourself wanting to write "see above" / "上面对话", sub-task is non-trivial, user said "派个子 agent" / "spawn agent" / "delegate" / "fork 出去" / "sub-agent 干". - TRIGGER PHRASES: "派个子 agent", "spawn agent", "让子 agent 干", "delegate", "sub-agent", "把任务交出去", "fork 出去", "background task", "派发", "子 agent 干", "子任务". - SKIP WHEN: sub-agent needs verbatim context (rare; usually `read` / `grep` is faster), sub-task boundary is fuzzy (decompose first via `plan-stream-emit`), work is so small brief would be longer than the work itself. + Hand off a sub-task to a sub-agent with a tight, complete brief — 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: Requires MiniMax Code with Agent Plugins 1.0 support. +compatibility: Requires MiniMax Code with Agent Plugins 1.0 support. The example calls in this Skill are written in Codex-harness style (pseudocode) using `subagent=...` and `task_name=...`; MiniMax Code's `task` tool may use different parameter names. Adapt the call shape to the actual host API. metadata: author: antianqi - version: "1.0.1" - inspired-by: https://github.com/openai/codex/blob/main/codex-rs/protocol/src/protocol.rs (Op::InterAgentCommunication, CollabAgentSpawnBegin) and core/src/session/multi_agents.rs - changes-from-v0.2.0: "Added the message envelope format (Message Type / Task name / Sender / Payload) from P-20 V2; added explicit 'this is the sub-agent return path' section; cross-referenced fork-context-decision for fork_turns choice." + version: "1.0.2" + inspired-by: https://github.com/openai/codex/blob/main/codex-rs/protocol/src/protocol.rs (InterAgentCommunication) and core/src/session/multi_agents.rs (CollabAgentSpawn) + changes-from-v1.0.1: "Rewritten to be host-agnostic. The 4-part envelope design is preserved (it is the portable part); example calls are now pseudocode with an explicit mcode adaptation note. Removed hard-coded `subagent=explore` and `task_name=...` examples." --- -# Delegate With Context +# Delegate with Context -When you spawn a sub-agent (`task` or equivalent), the brief you pass is the only thing -it sees. A bad brief makes the sub-agent re-derive the whole conversation; a good brief -gives it exactly what it needs to do its job and nothing more. +When handing off work to a sub-agent, the agent has two extremes: -This Skill is the inverse of "pass the full history" — the full history is the most -expensive context you can give a sub-agent, and it is rarely what the sub-agent needs. +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. -**v1.0 update**: now includes the message envelope format used by Codex's V2 multi-agent -protocol, and a clear "this is the return path" section so the sub-agent knows how to -deliver its result. +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 适配**:本 Skill 的 example 调用用 Codex-harness 风格(`task(subagent=explore, +> task_name=..., brief=...)`)作为**伪代码**。MiniMax Code 的 `task` 工具可能用不同参数名 +> (`agent_type=...` / `name=...` / `brief=...`)。请**根据实际 host API 改写参数名**。 ## 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. -- The full conversation history is > 30 turns or contains large tool outputs the sub-agent - does not need. -- The sub-task has a clear boundary (file, function, doc, test) that can be described in one - sentence. -- You find yourself wanting to write "see the conversation above" — that is the trigger to - stop and write an actual brief. +- 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-agent is a simple one-shot lookup that needs verbatim context (rare; usually a - `read` or `grep` is faster than a sub-agent). -- The sub-task boundary is fuzzy. If you cannot name the boundary, you cannot brief it — - decompose first, then delegate. -- The work is so small that the brief would be longer than just doing it. +- 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. **Write the brief as a fenced block** in this exact shape, **before** calling `task`: - - ```markdown - ## Sub-task brief - - **Goal** (one sentence, in the user's own words if possible): - <...> - - **Boundary** (what is in scope, what is out of scope): - - In: <...> - - Out: <...> - - **Inputs** (only what the sub-agent needs to read; absolute paths): - - /path/to/file.rs (function `foo`) - - /path/to/spec.md (section 3.2 only) - - - - **Pass condition** (one checkable sentence): - <...> - - **Output shape** (what the sub-agent should return): - - A patch, a report, a single sentence, a JSON object — be specific. - - If returning code, name the file path the patch should land in. - - **Constraints** (what NOT to do, to save round trips): - - Do not refactor adjacent code. - - Do not change the public API. - - Do not introduce new dependencies. - - - - **Return path** (how the sub-agent reports back; see v1 message envelope below): - - Reply on the analysis channel with this exact envelope: - ``` - Message Type: FINAL_ANSWER - Task name: - Sender: - Payload: - - ``` - - Keep the payload under ~10 lines unless the task is "produce a long report." +1. **Classify the sub-task** (see `fork-context-decision`): + - Self-contained: `none` (just the brief). + - Needs prior context: `N` or `all`. +2. **Decide the sub-agent type** (explore / worker / verifier / etc.) based on what + the sub-task needs. +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`). +5. **Document the return path** — how the sub-agent should hand the result back. - **Model tier** (cheap / medium / main; see `model-router` Skill): - - - ``` +## The 4-part envelope -2. **Choose `fork_turns`** explicitly (see `fork-context-decision` Skill): - - Self-contained sub-task → `none` - - Needs recent context → small `N` - - Continuation of same debugging session → `all` (rare) - -3. **Call `task`** with the brief as the prompt. The full conversation history is *not* - in the prompt; the brief is. - -4. **Verify the brief round-tripped.** Read the sub-agent's first response. If it is solving - the wrong problem, your brief failed — do not let it finish. Stop and re-brief. - -5. **Receive the result** in the message envelope format. The sub-agent's reply should - match the envelope; if it doesn't, treat the reply as unverified raw output and re-parse. - -6. **If the sub-agent needs more context mid-task**, send a follow-up brief in the same - shape, not the original full history. - -7. **On return, validate against the pass condition.** If unmet, re-dispatch with a tighter - brief; do not patch the result yourself unless the fix is trivial. - -8. **Record in the family file** (see `subagent-family-tracking` Skill) so the tree stays - up to date. - -## Message envelope (V2 protocol) - -Codex's V2 multi-agent protocol uses a structured envelope for sub-agent replies: +Every sub-agent brief MUST have these 4 parts, in order: ```text -Message Type: -Task name: -Sender: -Payload: - +Task name: +Sender: +Task: +Payload: +Return: ``` -When your sub-agent replies, **expect this envelope** and parse it accordingly. If the -reply is plain prose with no envelope, treat it as `MESSAGE` (an interim update, not the -final answer) and either wait for the `FINAL_ANSWER` or re-brief to clarify. - -## Output contract +Each part is mandatory. Skipping any one is the difference between a working +sub-task and a confused one. -The user sees, in this order: +### Field-by-field -- The Sub-task brief block (before the `task` call), including the chosen `fork_turns`. -- The `task` invocation (one line, with the `task_name`). -- The sub-agent's reply, parsed (envelope + payload). -- (If failed) the re-brief, not a silent retry. +| 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.` | -## Example - -```markdown -## Sub-task brief - -**Goal**: Add a single function `format_currency(amount: f64, currency: &str) -> String` to -`src/money.rs` that formats USD with two decimals, EUR with symbol suffix, JPY with no decimals. - -**Boundary**: -- In: one new function + 4 unit tests -- Out: refactoring `money.rs`, changing existing callers, adding a new file - -**Inputs**: -- /repo/src/money.rs (read top of file to see the existing style) - -**Pass condition**: -- `cargo test money::` green -- `format_currency(1234.5, "USD") == "$1,234.50"` -- `format_currency(1234.5, "EUR") == "1,234.50 €"` -- `format_currency(1234.0, "JPY") == "¥1,234"` - -**Output shape**: a unified diff against `/repo/src/money.rs`. - -**Constraints**: -- No new dependencies (no `rust_decimal`, `num-format`, etc.) -- Match the existing function signature style in `money.rs` - -**Return path**: reply on the analysis channel with `Message Type: FINAL_ANSWER, Sender: -, Payload: ` - -**Model tier**: cheap — single-function reformat, no judgement needed -``` +## Common pitfalls -Then: +- **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 Codex-only `subagent=...` / `task_name=...` parameter names** — + Codex-harness style. Adapt to the actual host API. -```text -> task(subagent=explore, run_in_background=true, fork_turns=0, - prompt="") -launched explore (id: 5a3f); fork=none, tier=cheap -``` +## Example -Sub-agent reply (parsed): +The example below is **Codex-harness style pseudocode** for clarity. On MiniMax +Code, the `task` tool's parameter names are **not exposed** as shown; adapt to +the actual host API. ```text -Message Type: FINAL_ANSWER -Task name: money-format -Sender: explore-5a3f -Payload: -Added `format_currency` to src/money.rs:42-58, plus 4 unit tests at lines 78-110. All -pass. Diff at /tmp/money-diff.patch. +# Codex-harness style (pseudocode for design clarity): +> task( + subagent=explore, # ← replace with mcode's actual param + task_name="investigate-lint-flake", # ← ditto + fork_turns=0, # ← ditto + brief=""" + Task name: investigate-lint-flake + Sender: main agent + Task: Investigate why test_lint.py flakes on Windows but not Linux. + Produce a 1-paragraph root-cause analysis. + Payload: /home/user/proj/tests/test_lint.py (line 47 is the failure); + prior turn tool output: + Return: Append a section to /notes/lint.md titled + "## Windows flake root cause" with 1 paragraph. + """ + ) + +# MiniMax Code style (fill in the real host API): +# Replace subagent=, task_name=, fork_turns= with whatever mcode's +# task tool actually accepts. The 4-part envelope inside `brief=` +# is the portable part and should be preserved as-is. ``` -## Common pitfalls - -- **Do not pass the full conversation history as context.** That is the failure mode this - Skill exists to prevent. Pass the brief. -- **Do not write a brief that says "see above".** The sub-agent does not have "above". -- **Do not omit the pass condition.** Without it, the sub-agent picks its own definition - of done, which is rarely yours. -- **Do not omit the constraints.** "Don't refactor adjacent code" saves a 3-message - ping-pong. -- **Do not over-brief.** A 200-line brief for a one-function change is itself a token - waste. -- **Do not under-brief.** "Look at the auth code" is a wish, not a brief. -- **Do not brief a sub-task boundary that is fuzzy.** Decompose first - (`plan-stream-emit`), then brief the resulting steps. -- **Do not expect prose replies.** If the reply is not in the envelope format, treat it - as unverified and re-brief the sub-agent to use the envelope. -- **Do not forget to record in the family file.** If you do not, the next turn does not - know which sub-agent did what. +The **envelope** is the design; the **call shape** is host-specific. ## Verification checklist -- [ ] Did you write the Sub-task brief block before calling `task`? -- [ ] Is the goal one sentence in the user's voice? -- [ ] Is the boundary explicit (in / out)? -- [ ] Are the inputs absolute paths, not "look around the repo"? -- [ ] Is the pass condition one checkable sentence? -- [ ] Is the output shape specific (patch / report / JSON)? -- [ ] Did you list the constraints to head off re-work? -- [ ] Did you specify the return-path envelope? -- [ ] Did you choose `fork_turns` explicitly (`fork-context-decision`)? -- [ ] Did you choose the model tier (`model-router`)? -- [ ] Did the sub-agent's first response show it understood the brief? -- [ ] Did you record the spawn in the family file (`subagent-family-tracking`)? +- [ ] Did you classify the sub-task (self-contained vs context-dependent)? +- [ ] 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 adapt Codex-only parameter names to the actual host `task` API? 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 index f6b3cd6..19f0a39 100644 --- a/plugins/antianqi/codex-harness-patterns/skills/fork-context-decision/SKILL.md +++ b/plugins/antianqi/codex-harness-patterns/skills/fork-context-decision/SKILL.md @@ -1,26 +1,31 @@ --- name: fork-context-decision description: | - Pick `fork_turns` = all / N / none for sub-agent context size. - 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" / "传 history" / "just the brief" / "fork 0" / "fork all" / "不用 fork" / "不要带 context". + Decide how much parent context to pass to a sub-agent 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" / "传 history" / "just the brief" / "don't carry context" / "不用 fork" / "不要带 context". TRIGGER PHRASES: "fork 多少", "give it the full history", "不用 fork", "传 history", "just the brief", "不要带 context", "fork 0", "fork all", "传全部对话", "不带 context". - SKIP WHEN: sub-agent tool does not support `fork_turns`, already decided `none` (no decision to make). + SKIP WHEN: sub-task is trivial (one-line read), you have already decided "no context" (no decision to make). license: Apache-2.0 -compatibility: Requires MiniMax Code with Agent Plugins 1.0 support. +compatibility: Requires MiniMax Code with Agent Plugins 1.0 support. The example calls in this Skill are written in Codex-harness style (pseudocode); MiniMax Code's `task` tool may use a different parameter name for the same concept (e.g. `brief` vs `fork_turns`) — adapt the call to the actual host API, do NOT blindly use the parameter names below. metadata: author: antianqi - version: "0.1.1" - inspired-by: https://github.com/openai/codex/blob/main/codex-rs/core/src/session/multi_agents.rs + version: "0.1.2" + inspired-by: https://github.com/openai/codex/blob/main/codex-rs/core/src/session/multi_agents.rs (design principle only; the example parameter names are Codex-specific) + changes-from-v0.1.1: "Rewritten to be host-agnostic. The example calls are now pseudocode (Codex-style) with an explicit mcode adaptation note. The Skill teaches the decision, not the parameter spelling." --- # Fork Context Decision -`fork_turns` is the **single largest cost lever** in multi-agent work. `all` doubles your -context, `none` forces the sub-agent to re-derive from a brief, and the integer in between -is a precision knob. Pick wrong and you either pay main-model prices for routine work, or -you spawn a sub-agent that cannot do its job because it cannot see what came before. +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 you make it explicitly, not by accident. +This Skill codifies the decision so the agent makes it explicitly, not by accident. + +> **mcode 适配**:本 Skill 的 example 调用用 Codex-harness 风格(`fork_turns=N`)作为 +> **伪代码**。MiniMax Code 的 `task` 工具可能用不同参数名表达同一概念(如 `brief` +> vs `fork_turns`)。请**根据实际 host API 改写参数名**,不要盲抄。 ## When to use @@ -34,111 +39,90 @@ Activate when **any** of these is true: ## When NOT to use -- The sub-agent tool does not support a fork / context parameter (this Skill is then - irrelevant; skip). -- The sub-task is so trivial that the cost difference is noise (a one-line `grep`). -- You have already decided `fork_turns=0` or `none` (i.e. you have already chosen "no - context") and there is no decision to make. - -## The 3 fork modes +- 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). -| Mode | What the sub-agent sees | Cost | When to use | -|---|---|---|---| -| **`all`** (default if omitted) | All of parent's history | **High** — sub-agent pays for every turn you took | Sub-agent needs the *exact* reasoning that led to the current state. Rare. | -| **`N`** (positive integer) | Last N turns of parent | **Medium** — proportional to N | Sub-agent needs the *recent* context (the last few tool calls / decisions) but not the full history. Most common. | -| **`none`** (or `0`) | Nothing — pure brief | **Low** — only the brief | Sub-agent can do its job from a well-written brief alone. The default for `parallel-fanout`. | +## The decision -## Decision framework +Three choices, ordered by cost: -Before every `task` call, ask: +| Choice | What the sub-agent sees | Use when | +|---|---|---| +| **all** | The full parent conversation history. | 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. | 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. | -1. **Can the sub-agent do the job from the brief alone?** - - **Yes** → `none` - - **No** → continue +### Pseudo-cost table -2. **Does it need recent decisions / tool outputs to do the job?** - - "Recent" = the last 3-10 turns - - **Yes** → `N` where N ≈ the number of turns that contain the needed context - - **No** → continue +| 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) | -3. **Does it need the full reasoning that led here?** - - This is rare. Most sub-tasks do not. - - **Yes** → `all` (and consider whether the sub-agent should be a continuation of the - main agent instead of a fresh sub-agent) +## Process -4. **Will the cost of `all` be acceptable?** - - If `all` would push the sub-agent over its own budget, choose `N` or `none`. - - If the sub-task is cheap and the cost of failure is high, `all` may be worth it. +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. **Document the choice in the brief**: + - `Context: last 3 turns (decided N=3 because the sub-task needs the prior tool output).` +4. **If the sub-agent fails**, retry with the next higher N before changing anything + else. ## Output contract -Every time you call `task`, the user sees: +After activating this Skill, the next `task` call MUST include the chosen context level, +either as a parameter or in the brief header: -- The chosen mode (`all` / `N` / `none`) in the brief or in a one-line preamble. -- A one-sentence reason: "fork=N because the brief alone misses the X decision made 3 - turns ago." +```text +# Sub-task brief +Context level: +Reason: + +``` -## Process +## Common pitfalls -1. **State the chosen mode** in the brief, before writing the rest of it. This forces an - explicit decision. -2. **For `N`**, name the specific turns / events the sub-agent needs to see. Do not just - write `N=5`; write `N=5 because the last 5 turns contain the X decision`. -3. **For `none`**, the brief must be self-contained. If the brief references "the - conversation above" or "what we just decided," you have made a mistake — `none` requires - a complete brief. -4. **For `all`**, explicitly justify why the sub-agent needs the full history. Default - suspicion: you do not need `all`; you need `N`. +- **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. ## Example -```text -> task(subagent=explore, run_in_background=true, - fork_turns=0, - prompt="") -launched explore (id: 5a3f); fork=none because the brief is self-contained -``` - -```text -> task(subagent=explore, run_in_background=true, - fork_turns=3, - prompt="The last 3 turns contain the design decision; resume from there. - ") -launched explore (id: 6b2c); fork=3 because the sub-task picks up after the auth redesign -decision -``` +The example below is **Codex-harness style pseudocode** for clarity. On MiniMax Code, +the `task` tool's parameter name for "how much parent context to share" may be +`brief` (a string) rather than `fork_turns` (an integer). **Adapt the call shape to +the actual host tool, do not copy this verbatim**: ```text -> task(subagent=explore, run_in_background=true, - fork_turns=all, // rare - prompt="") -launched explore (id: 7c1a); fork=all because this sub-agent IS the continuation of the -debugging session +# Codex-harness style (pseudocode for design clarity): +> task( + subagent=explore, + fork_turns=3, # ← replace with the actual mcode param + brief="Investigate why test_lint.py flakes on Windows. ..." + ) + +# MiniMax Code style (actual API, fill in the real param name): +> task(agent_type="explore", brief="...") # if mcode uses a `brief` field +> task(subagent="explore", history="last-3") # if mcode uses a `history` field ``` -## Common pitfalls - -- **Do not default to `all`.** It is the most expensive answer. The Skill exists to move - work *off* `all`, not to confirm the obvious. -- **Do not default to `none` if the brief is incomplete.** An under-forked sub-agent will - fail silently. It is better to over-fork than under-fork on the first attempt; downgrade - on retry. -- **Do not pick `N` without naming the turns.** "N=5" is not a decision; "N=5 because the - last 5 turns contain the X decision" is. -- **Do not pick `all` "to be safe."** It is not safer; it is expensive. Safety comes from - a well-scoped brief + an explicit decision, not from dumping history. -- **Do not change the fork mode mid-stream.** If you started with `none` and the - sub-agent comes back saying "I need more context," do not re-spawn with `all`; instead, - send a follow-up `send_message` (or equivalent) with the specific context it needs. - Re-spawning wastes the work it already did. +The **decision** (3 turns) is the same; the **spelling** depends on the host. ## Verification checklist -- [ ] Did you choose `all` / `N` / `none` explicitly, not by leaving the default? -- [ ] Is the chosen mode justified in the brief or in a one-line preamble? -- [ ] For `N`: did you name the specific turns the sub-agent needs? -- [ ] For `none`: is the brief self-contained (no references to "above" or "earlier")? -- [ ] For `all`: did you explicitly justify why the full history is needed? -- [ ] Is the cost of the chosen mode acceptable (no surprise over-budget)? -- [ ] If a sub-agent came back saying "I need more context," did you send a follow-up - message rather than re-spawning with `all`? +- [ ] Did you classify the sub-task before choosing? +- [ ] Did you pick the smallest N that works (not jumping straight to `all`)? +- [ ] Did you document the choice in the brief header? +- [ ] If the sub-agent failed, did you bump N before changing the brief? +- [ ] Did you adapt the example parameter names to the actual host `task` API? diff --git a/plugins/antianqi/codex-harness-patterns/skills/model-router/SKILL.md b/plugins/antianqi/codex-harness-patterns/skills/model-router/SKILL.md index 623eeb6..527b0fa 100644 --- a/plugins/antianqi/codex-harness-patterns/skills/model-router/SKILL.md +++ b/plugins/antianqi/codex-harness-patterns/skills/model-router/SKILL.md @@ -4,13 +4,14 @@ description: | Classify sub-task complexity (cheap / medium / main) and pick matching `model_config_id`. USE WHEN: about to call `task()` for non-trivial sub-task, about to spend main model on work cheap model could do, "do this with the cheap model" / "用便宜模型" / "不要用主模型" / "sub-task 不重" / "small task", sub-task is routine lookup / reformat / list / reformat-only, "this is just a grep" / "this is just a reformat" / "小任务". TRIGGER PHRASES: "用便宜模型", "cheap model", "use the cheap model", "小任务用便宜模型", "不要用主模型", "用本地模型", "sub-task 不重", "小任务", "this is just a", "小 case 用便宜". - SKIP WHEN: sub-task IS the main task (no delegation), sub-agent tool does not support `model_config_id`, sub-task is genuinely synthesis / design / cross-file reasoning. + SKIP WHEN: sub-task IS the main task (no delegation), sub-agent tool does not support a model / tier parameter, sub-task is genuinely synthesis / design / cross-file reasoning. license: Apache-2.0 -compatibility: Requires MiniMax Code with Agent Plugins 1.0 support. +compatibility: Requires MiniMax Code with Agent Plugins 1.0 support. The example calls in this Skill reference `model_config_id` and `reasoning_effort` as Codex-harness style pseudocode; MiniMax Code's actual model-routing API may differ. Adapt the call to the real host API — do not copy the parameter names verbatim. metadata: author: antianqi - version: "0.1.1" - inspired-by: https://github.com/openai/codex/blob/main/codex-rs/model-provider-info/ and codex-rs/models-manager/ + version: "0.3.2" + inspired-by: https://github.com/openai/codex/blob/main/codex-rs/model-provider-info/ and codex-rs/models-manager/ (design principle only; example model names are Codex-specific) + changes-from-v0.1.1: "Removed hard-coded `model_config_id: anthropic-sonnet-4 with reasoning_effort=high` example. Replaced with a portable 3-tier decision rubric and an explicit mcode 适配 note." --- # Model Router @@ -20,9 +21,15 @@ The main model is expensive and slow. Most sub-tasks a long agent spawns are not Codex harness routes those to cheaper models and reserves the main model for synthesis and hard reasoning. +> **mcode 适配**:本 Skill 提到 `model_config_id` 和 `reasoning_effort` 是 Codex-harness +> 风格的**伪代码**。MiniMax Code 当前的 model 路由有自己的命名约定,可能通过 +> `llm-call` skill 或 host 设置暴露。请**根据实际 host 模型名替换**。Skill 的 +> **设计原则**(3-tier / 不要默认 main / cheap 默认)是 portable 的,model 名字是 +> host-specific 的。 + This Skill codifies that routing: before every `task` call, classify the sub-task and pick -the right `model_config_id`. The savings are not theoretical — the same model router that -gave Codex a 6× token reduction on context compaction works the same way on delegation. +the right tier. The savings are not theoretical — the same model router that gave +Codex a 6× token reduction on context compaction works the same way on delegation. ## When to use diff --git a/plugins/antianqi/codex-harness-patterns/skills/parallel-fanout/SKILL.md b/plugins/antianqi/codex-harness-patterns/skills/parallel-fanout/SKILL.md index 84f44eb..750085c 100644 --- a/plugins/antianqi/codex-harness-patterns/skills/parallel-fanout/SKILL.md +++ b/plugins/antianqi/codex-harness-patterns/skills/parallel-fanout/SKILL.md @@ -1,198 +1,127 @@ --- name: parallel-fanout description: | - Dispatch 2+ independent sub-tasks in parallel via `task` and aggregate. - USE WHEN: 2+ independent sub-tasks, each bounded and well-defined, serial would take 2x longer than longest sub-task, user said "并行" / "parallel" / "fan out" / "spawn agents", multiple independent files/probes/analyses, "for each of A/B/C" / "分头做" / "拆开". - TRIGGER PHRASES: "并行", "parallel", "fan out", "spawn agents", "分头做", "一起做", "for each", "分别", "拆开并行", "一起跑". - SKIP WHEN: sub-tasks share state, sub-tasks depend on each other's output, total work is tiny (< 3 edits), user said "one by one" / "step by step" / "sequentially" / "一个一个来". + Decompose a task into 2+ truly independent sub-tasks and dispatch them concurrently. 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: Requires MiniMax Code with Agent Plugins 1.0 support. +compatibility: Requires MiniMax Code with Agent Plugins 1.0 support. The example calls in this Skill are written in Codex-harness style (pseudocode) using `subagent=...` and `fork_turns=...`; MiniMax Code's `task` tool may use different parameter names. Adapt the call shape to the actual host API. metadata: author: antianqi - version: "1.0.1" - inspired-by: https://github.com/openai/codex/blob/main/codex-rs/core/src/session/multi_agents.rs and core/src/thread_manager.rs - changes-from-v0.1.0: "Added explicit-spawn principle (P-20: spawn is opt-in, not auto); added `max_concurrency` awareness; cross-referenced `fork-context-decision` for per-sub-task cost control; cross-referenced `delegate-with-context` for the brief." + version: "1.0.2" + inspired-by: https://github.com/openai/codex/blob/main/codex-rs/core/src/thread_manager.rs (design principle; example parameter names are Codex-specific) + changes-from-v1.0.1: "Rewritten to be host-agnostic. Example calls are now pseudocode with an explicit mcode adaptation note. The Skill teaches the fan-out decision, not the parameter spelling." --- -# Parallel Fan-Out +# Parallel Fanout -When a task splits cleanly into 2+ independent sub-tasks, dispatch them in parallel and -aggregate. Skip when the sub-tasks have data dependencies, when serial execution is fast -enough that the overhead of fan-out is not worth it, or when the user has not opted in to -multi-agent work. +When the user task is clearly decomposable into 2+ **truly independent** sub-tasks, the +agent has two choices: -**v1.0 update**: makes explicit that fan-out is an *opt-in decision* (mirroring Codex's -`MultiAgentMode::ExplicitRequestOnly` default), and ties fan-out to the cost-control -machinery in `fork-context-decision`. +1. **Serialize**: do them one by one, holding the conversation hostage. +2. **Fan out**: dispatch them concurrently, aggregate the results. -## When to use - -Activate when **all** of the following hold: - -- The user task has 2+ **independent** sub-tasks. Independent means: sub-task B does not need - any output of sub-task A, and sub-task A does not need any output of sub-task B. -- Each sub-task is **bounded**: you can name what "done" looks like in one sentence. -- The estimated wall-clock time of serial execution is **at least 2×** the longest single - sub-task. If one sub-task dwarfs the others, fan-out buys you little. -- The user has **opted in** to multi-agent work for this task. See "Opt-in principle" below. -- The user has not said "do it one by one" / "step by step" / "sequentially please". +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. -### Opt-in principle (from Codex MultiAgentMode) +> **mcode 适配**:本 Skill 的 example 调用用 Codex-harness 风格(`task(subagent=..., +> fork_turns=...)`)作为**伪代码**。MiniMax Code 的 `task` 工具可能用不同参数名 +> (`agent_type` / `brief` / `history` 等)。请**根据实际 host API 改写参数名**, +> 不要盲抄。 -Codex ships in `MultiAgentMode::ExplicitRequestOnly`: the agent does not spawn sub-agents -on its own — only when the user explicitly asks or the system instruction is set. We follow -the same default: +## When to use -- **Default**: do not fan out. Do the work yourself, or in sequence. -- **Opt-in triggers** (any one is enough): - - The user says "并行" / "fan out" / "do these in parallel" / "spawn agents". - - The user has set an AGENTS.md / system instruction that says "use sub-agents for this - class of task." - - The task is so large that serial execution would obviously exceed the user's patience - (judgment call — be conservative). +Activate when **any** of these is true: -When in doubt, ask the user before fan-out. The cost of a wrong fan-out is wasted sub-agent -spend; the cost of a wrong serial is just a few extra turns. +- 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 share state (e.g. all read/modify the same file in conflicting ways). -- The sub-tasks need each other's intermediate output (e.g. test plan A depends on the refactor - in B). -- The total work is tiny (3 file edits); the orchestration overhead exceeds the savings. -- The user explicitly asked for serial work or a careful step-by-step walkthrough. -- You cannot articulate the boundary of each sub-task in one sentence. If you can't, you can't - safely parallelise it. -- The user has not opted in to multi-agent work and the task is small enough to do - directly. +- 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. **State the decomposition first** — before any tool call, write a fenced block that names - the sub-tasks, their boundaries, and the aggregation step: - - ```markdown - ## Fan-out plan - - **Sub-task 1**: - **Sub-task 2**: - **Sub-task 3**: - - **Aggregation**: - **Stop conditions**: - **Concurrency cap**: - ``` - -2. **For each sub-task, decide `fork_turns`** (see `fork-context-decision` Skill): - - Self-contained sub-task (look up, reformat, list) → `none` - - Sub-task depends on recent parent decisions → `N` (small) - - Sub-task is a continuation of the same debugging session → `all` (rare) - -3. **Dispatch in parallel** with `task`. Use `run_in_background: true` for each so they overlap - in the same round trip. Pass a **minimal-context brief** to each (see - `delegate-with-context` Skill) — not the full history. - -4. **Respect the concurrency cap.** If the user task has 8 sub-tasks and `max_concurrency` - is 5, dispatch 5, wait for one to finish, then dispatch the next. Do not fan out - unboundedly — the harness and the user's patience both have limits. +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). + - Needs prior context? `N` or `all`. +3. **Check the host's concurrency cap**. Don't fan out 50 sub-tasks if the host + caps at 8. The agent should respect the host's buffer-unordered limit, not + assume unlimited concurrency. +4. **Dispatch the batch**. The agent SHOULD wait for all to complete before + aggregating — partial results are usually not useful. +5. **Aggregate per sub-task**. 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). -5. **While waiting**, the orchestrating agent may draft the aggregation template (so the final - merge is a fill-in, not a re-derivation). - -6. **On all sub-tasks completing**: - - Verify each met its pass condition. - - If a sub-task drifted outside its boundary, **reject** and re-dispatch with a tighter - brief. Do not absorb the drift. - - If two sub-tasks produced conflicting facts (different numbers, different recommendations), - surface the conflict to the user **before** aggregating. Do not silently pick one. - -7. **Aggregate** into the agreed shape. Cite the source sub-task for each section so the user - can drill in. - -8. **Report** the wall-clock time saved if you have it (use timestamps from the sub-task - responses). This is how you earn the right to fan out again. +## Output contract -9. **Before declaring the whole task done**, run a `completion-audit` (separate Skill) on - the aggregated result. Fan-out is exactly the kind of work that produces confident-looking - but unverified deliverables. +After activating this Skill, the agent's next message MUST include: -## Output contract +- 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). -The user sees, in this order: +## Common pitfalls -- The Fan-out plan block (before any tool call), including the concurrency cap. -- The list of dispatched sub-tasks (one line per `task` call) with the chosen `fork_turns`. -- The pass/fail per sub-task. -- Any conflicts surfaced before aggregation. -- The aggregated result. -- (Optional) The wall-clock saving vs serial. -- A `completion-audit` on the final aggregation before declaring done. +- **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 the host's concurrency cap silently** — the host will queue or fail. + Check the cap first. +- **Aggregating without verification** — one sub-task may have silently failed. + Always read each output. +- **Using Codex-only `subagent=...` / `fork_turns=...` parameter names** — those + are Codex-specific. Adapt to the actual host. ## Example -```markdown -## Fan-out plan - -**Sub-task 1**: Audit dependencies in /repo/server/Cargo.toml for known CVEs. - Pass condition: a table of {crate, version, advisory_id, severity}. - fork_turns: none (look-up only). -**Sub-task 2**: Audit dependencies in /repo/web/package.json for known CVEs. - Pass condition: a table of {package, version, advisory_id, severity}. - fork_turns: none. -**Sub-task 3**: List license of every direct dependency in /repo/server and /repo/web. - Pass condition: a single table of {crate_or_package, license, copyleft_flag}. - fork_turns: none. - -**Aggregation**: Combine into a single SECURITY-REPORT.md at the repo root. -**Stop conditions**: If sub-task 1 or 2 finds a critical CVE, surface immediately and do not -wait for sub-task 3. -**Concurrency cap**: 3 (we have 3 sub-tasks, all run in parallel). -``` - -Then: +The example below is **Codex-harness style pseudocode** for clarity. On MiniMax Code, +the `task` tool's parameter names are **not exposed** as shown; adapt to the +actual host API. ```text -> task(subagent=explore, run_in_background=true, fork_turns=0, - prompt="Audit /repo/server/Cargo.toml direct dependencies ...") -> task(subagent=explore, run_in_background=true, fork_turns=0, - prompt="Audit /repo/web/package.json direct dependencies ...") -> task(subagent=explore, run_in_background=true, fork_turns=0, - prompt="List licenses of /repo/server and /repo/web direct deps ...") +# Codex-harness style (pseudocode for design clarity): +Sub-tasks: A, B, C +Concurrency cap: 8 (from host config) + +> task(subagent=explore, fork_turns=0, + brief="A: look up X in repo 1") +> task(subagent=explore, fork_turns=0, + brief="B: look up Y in repo 2") +> task(subagent=explore, fork_turns=0, + brief="C: look up Z in repo 3") + +# (Agent waits for all three.) +# Aggregator reads each output, audits per `completion-audit`. + +# MiniMax Code style (fill in the real host API): +# Replace subagent=... with whatever mcode's task tool uses +# (e.g. agent_type="explore"), and fork_turns=0 with the actual +# context-sharing parameter or remove it. ``` -## Common pitfalls - -- **Do not fan out by default.** Codex ships in `MultiAgentMode::ExplicitRequestOnly` — - spawn is opt-in, not auto. Follow the same default. -- **Do not fan out work that is too small.** A 200-line refactor is one task, not three. -- **Do not fan out work with hidden dependencies.** If sub-task 2 might need to read what - sub-task 1 wrote, that's serial. Don't pretend. -- **Do not over-brief sub-tasks.** "Refactor the auth subsystem" is not a sub-task brief; it - is the whole job. A sub-task brief names a file, a change, and a pass condition. -- **Do not under-brief.** "Look at the auth code" is not enough; the sub-agent will guess - wrong. Always include the file paths and the pass condition. -- **Do not aggregate silently.** If two sub-tasks disagree, the user must see the conflict. -- **Do not fan out > 5 sub-tasks.** Beyond 5, the aggregation step becomes a bottleneck and - context cost grows. For larger splits, ask the user first. -- **Do not skip the completion audit on the aggregation.** Fan-out produces more text - to be wrong about, not less. A confident-looking aggregated report is still unverified - until the audit is run. -- **Do not pick `all` for fan-out sub-tasks.** Fan-out sub-tasks should default to - `none` (look-up only) or small `N` (recent context). See `fork-context-decision`. -- **Do not exceed the concurrency cap.** If `max_concurrency=5` and you have 8 sub-tasks, - batch them, not all at once. +The **decision** (3 sub-tasks, `none` context, wait-for-all) is the same; the +**spelling** depends on the host. ## Verification checklist -- [ ] Did the user opt in (or is the task so large that opt-in is the only reasonable read)? -- [ ] Did you state the Fan-out plan block before any tool call, including the - concurrency cap? -- [ ] Is each sub-task truly independent (no shared state, no data flow between them)? -- [ ] Did you pass a minimal-context brief, not the full history? (`delegate-with-context`) -- [ ] Did you choose `fork_turns` for each sub-task explicitly? (`fork-context-decision`) -- [ ] Did you use `run_in_background: true` so they overlap? -- [ ] Did you respect the concurrency cap (no unbounded fan-out)? -- [ ] Did you surface conflicts before aggregating? -- [ ] Did you cite the source sub-task for each section of the aggregation? -- [ ] Did you run a `completion-audit` on the aggregation before declaring done? -- [ ] Did the wall-clock savings actually justify the fan-out? +- [ ] Did you write the sub-task list in the brief header before dispatching? +- [ ] Did you check the host's concurrency cap and stay under it? +- [ ] 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 adapt Codex-only parameter names to the actual host API? From 6f1a6150db52c9c4a6592464b79080743dcd920e Mon Sep 17 00:00:00 2001 From: antianqi Date: Tue, 25 Aug 2026 17:12:04 +0800 Subject: [PATCH 25/49] fix: add Host runtime requirements sections to plugin-author-helper and long-term-memory (reviewer feedback) Resolves the second half of the hetaoBackend CHANGES_REQUESTED review (PR #18 reviewer point 3). Both Skills describe design patterns that *would* involve network calls, file writes, or background tasks if the host's runtime ever implemented them. The original wording presented these as if the agent could execute them directly. Reviewer flagged this as unsafe. Both Skills now carry an explicit 'Host runtime requirements' section that: 1. Lists the side effects the Skill's design presumes (network, filesystem writes, sub-agent spawn, schedule triggers, secret redaction, etc.). 2. States that the agent MUST NOT execute any of these on the strength of the Skill alone. 3. Requires the host's normal user-confirmation policy (approval_policy / ask mode / equivalent) to be followed for any execution. 4. Reframes the Skill as DESIGN-only, not EXECUTE. plugin-author-helper and long-term-memory now have the same host boundary pattern as the other Skills (which already said 'Skills are pure Markdown instructions; the agent applies them with its existing tools and existing permission model'). No other content was changed. Test evidence: - 2 SKILL.md updated, each gained one new section - 0 existing content removed - 0 new side effects introduced --- .../skills/long-term-memory/SKILL.md | 23 +++++++++++++++++++ .../skills/plugin-author-helper/SKILL.md | 19 +++++++++++++++ 2 files changed, 42 insertions(+) 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 index 99c274d..b017901 100644 --- a/plugins/antianqi/codex-harness-patterns/skills/long-term-memory/SKILL.md +++ b/plugins/antianqi/codex-harness-patterns/skills/long-term-memory/SKILL.md @@ -34,6 +34,29 @@ Activate when designing any of: - 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. 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 index 8fc8f82..f51a7c7 100644 --- a/plugins/antianqi/codex-harness-patterns/skills/plugin-author-helper/SKILL.md +++ b/plugins/antianqi/codex-harness-patterns/skills/plugin-author-helper/SKILL.md @@ -35,6 +35,25 @@ Activate when: - 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 From 7f15cf1800ba24c55424cb2d191834aa0b7daac2 Mon Sep 17 00:00:00 2001 From: antianqi Date: Wed, 26 Aug 2026 08:35:38 +0800 Subject: [PATCH 26/49] =?UTF-8?q?feat(mcode-island):=20v0.3.0=20=E2=80=94?= =?UTF-8?q?=20io.minimax.mcode=20Hooks=20extension?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds a Plugin-format Hooks declaration under `io.minimax.mcode/hooks/` that conforms to the portable spec proposed in MiniMax-Code-Plugins PR #20 (companion to d86625d). mcode 0.2.4 already ships the runtime dispatch path for five of the twelve events; the remaining seven are forward-looking and declared so the validator can warn on them. The agent does not need to call `notify-island.ps1` manually when the runtime wires the Hooks path. The detector-based fallback in `mcode-status-detect.ps1` continues to run for everything else, so this change is strictly additive: no existing capability is removed or renamed. ## What changed - `plugin.json`: bumped 0.2.1 → 0.3.0, declared `extensions.io.minimax.mcode.hooks` so the registry validator (PR #20) recognizes the Plugin as having an io.minimax.mcode client extension. - `io.minimax.mcode/hooks/hooks.json`: 12-event declaration using only the portable field vocabulary (`command`, `args`, `env`, `cwd`, `matcher`, `pattern`, `regex`, `glob`, `timeout`, `timeoutMs`, `once`). No reserved fields. `PLUGIN_ROOT` is used for the script path; no host-absolute literals. - `io.minimax.mcode/hooks/scripts/_lib.ps1`: shared helper exporting `Read-HookStdin`, `Push-Island`, `Test-IsSelfPush`, `Format-ToolSummary`. Loaded via dot-source from every event script. The self-push filter avoids recursive state churn when the agent calls `notify-island.ps1` directly through Bash. - `io.minimax.mcode/hooks/scripts/.ps1` x 12: one script per event. State mapping: | event | pill state | notes | | ----------------- | ----------- | ----- | | SessionStart | idle | | | SessionEnd | idle | | | UserPromptSubmit | thinking | | | PreToolUse | working | skips self-push | | PostToolUse | done/error | heuristic on tool_result | | Stop | done | | | PreCompact | thinking | | | Notification | idle | | | SubagentStart | working | CODEX only | | SubagentStop | done | CODEX only | | PermissionRequest | waiting | returns `ask` (observer opt-in, see PR #20 §Decision semantics) | | PermissionDenied | error | | - `permission-request.ps1`: returns `{"decision":"ask",...}`, not `allow`, to comply with the portable observer invariant added in PR #20 commit 28aa5f4. The 0.2.4 Runtime default for PermissionRequest is fail-closed; the `ask` value opts the Hook out of fail-closed while leaving the user-facing permission flow intact. - `scripts/smoke.mjs`: pre-submit self-check. Zero dependencies (Node 18+ stdlib only), cross-platform. Validates `plugin.json` shape, the `extensions.io.minimax.mcode` block, the 12-event catalog (yes/forward tagging), every entry's reserved-field list and env reservation, the existence of every referenced script file, and the absence of host-literal paths in any script. - `SKILL.md` / `README.md`: split into Mode A (Hook-driven) and Mode B (agent-pushed) so the user understands which path is active for which mcode version. - `.gitattributes`: force LF for all source files. PowerShell 5.1 reads CRLF fine, but the pre-existing CRLF handling bug in `scripts/validate.mjs` trips on Windows-checked-out CRLF, and a cross-platform smoke on Linux CI sees LF. ## Test evidence End-to-end smoke (15/15) at @minimax-ai/code@0.2.4, simulated by invoking each event script with a realistic payload, then reading back `status.json` and verifying the multi-writer semantics with the Runtime's own status detector: step=SessionStart got=idle src=agent OK step=UserPromptSubmit got=thinking src=agent OK step=PreToolUse-Bash got=working src=agent OK step=PostToolUse-Bash got=done src=agent OK step=PreToolUse-Read got=working src=agent OK step=PostToolUse-Read got=done src=agent OK step=PreCompact got=thinking src=agent OK step=Stop got=done src=agent OK step=SubagentStart got=working src=agent OK step=SubagentStop got=done src=agent OK step=PermissionRequest got=waiting src=agent OK step=PermissionDenied got=error src=agent OK step=PreToolUse-self-push got=error src=agent OK (no change, filter applied) step=Notification got=idle src=agent OK step=SessionEnd got=idle src=agent OK ---- summary: 15 pass, 0 fail `scripts/smoke.mjs` on the in-repo tree: mcode-island v0.3.0 self-check [OK ] plugin.json parses [OK ] plugin.json: $schema is agent-plugins 1.0.0 [OK ] plugin.json: version is "0.3.0" [OK ] plugin.json: extensions.io.minimax.mcode is present [OK ] plugin.json: extensions.io.minimax.mcode.hooks resolves to io.minimax.mcode/hooks/hooks.json [OK ] io.minimax.mcode/hooks/hooks.json parses [WARN] event "Stop" is "forward" (not confirmed in @minimax-ai/code@0.2.4) [WARN] event "PreCompact" is "forward" (not confirmed in @minimax-ai/code@0.2.4) [WARN] event "Notification" is "forward" (not confirmed in @minimax-ai/code@0.2.4) [WARN] event "SubagentStart" is "forward" (not confirmed in @minimax-ai/code@0.2.4) [WARN] event "SubagentStop" is "forward" (not confirmed in @minimax-ai/code@0.2.4) [WARN] event "PermissionRequest" is "forward" (not confirmed in @minimax-ai/code@0.2.4) [WARN] event "PermissionDenied" is "forward" (not confirmed in @minimax-ai/code@0.2.4) [OK ] hooks.json[]: script .ps1 exists x 12 [OK ] _lib.ps1: shared helper present [OK ]