diff --git a/.gitattributes b/.gitattributes index d866327..8553f1b 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,4 +1,4 @@ -* text=auto +* text=auto eol=lf .gitattributes text eol=lf *.md text eol=lf *.toml text eol=lf diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml new file mode 100644 index 0000000..2f9e6ed --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -0,0 +1,52 @@ +name: バグ報告 +description: 再現可能な不具合を報告します +title: "[Bug] " +labels: + - bug +body: + - type: markdown + attributes: + value: | + セキュリティ上の問題は公開 issue に書かず、Security advisory から報告してください。 + - type: input + id: codex-version + attributes: + label: Codex のバージョン + placeholder: codex-cli 0.145.0 + validations: + required: true + - type: dropdown + id: platform + attributes: + label: 実行環境 + options: + - Windows / PowerShell + - Linux / Bash + - WSL / Bash + - その他 + validations: + required: true + - type: textarea + id: reproduction + attributes: + label: 再現手順 + description: 使用したコマンドと、最小の再現手順を書いてください。秘密情報は削除してください。 + validations: + required: true + - type: textarea + id: expected + attributes: + label: 期待した結果 + validations: + required: true + - type: textarea + id: actual + attributes: + label: 実際の結果 + validations: + required: true + - type: textarea + id: diagnostics + attributes: + label: 検証結果 + description: validator と `codex doctor --summary --no-color --ascii` の、秘密情報を除いた結果を書いてください。 diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000..3ba13e0 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1 @@ +blank_issues_enabled: false diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..8004983 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,7 @@ +version: 2 +updates: + - package-ecosystem: github-actions + directory: / + schedule: + interval: weekly + open-pull-requests-limit: 5 diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 0000000..f992e0f --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,14 @@ +## 変更内容 + +- (ここに記入) + +## 検証 + +- [ ] Windows の clean-home round trip +- [ ] Linux の clean-home round trip +- [ ] `git diff --check` +- [ ] README、CHANGELOG、公開契約への影響を確認 + +## リスクと互換性 + +- (ここに記入) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..004a427 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,217 @@ +name: CI + +on: + push: + branches: + - main + - "codex/**" + pull_request: + workflow_dispatch: + workflow_call: + schedule: + - cron: "17 3 * * 1" + +permissions: + contents: read + +env: + CODEX_PACKAGE: ${{ github.event_name == 'schedule' && '@openai/codex@latest' || '@openai/codex@0.145.0' }} + +jobs: + windows: + name: Windows clean-home round trip + runs-on: windows-latest + steps: + - uses: actions/checkout@v7 + + - uses: actions/setup-node@v7 + with: + node-version: 24 + + - name: Install validation runtime + shell: pwsh + run: | + npm install --global $env:CODEX_PACKAGE + codex --version + + - name: Parse PowerShell scripts + shell: pwsh + run: | + $failed = $false + Get-ChildItem -LiteralPath ./scripts -Filter '*.ps1' | ForEach-Object { + $tokens = $null + $errors = $null + [void][System.Management.Automation.Language.Parser]::ParseFile( + $_.FullName, + [ref]$tokens, + [ref]$errors + ) + if ($errors.Count -gt 0) { + $failed = $true + $errors | ForEach-Object { Write-Error $_ } + } + } + if ($failed) { throw 'PowerShell parsing failed.' } + + - name: Validate installer behavior + shell: pwsh + run: | + $cleanHome = Join-Path $env:RUNNER_TEMP 'task-aware-clean' + & ./scripts/Install-TaskAwareAgent.ps1 -CodexHome $cleanHome + & ./scripts/Test-TaskAwareAgent.ps1 -CodexHome $cleanHome -ConfigOnlyRuntime + & ./scripts/Install-TaskAwareAgent.ps1 -CodexHome $cleanHome + $backupCount = @(Get-ChildItem -Directory -LiteralPath (Join-Path $cleanHome 'task-aware-backups')).Count + if ($backupCount -lt 2) { throw 'Repeated installation reused a backup directory.' } + + $migrationHome = Join-Path $env:RUNNER_TEMP 'task-aware-migration' + New-Item -ItemType Directory -Force -Path $migrationHome | Out-Null + $migrationConfig = @( + '[features] # legacy feature section' + ' multi_agent = true' + '' + '[agents] # preserve this user comment' + ' enabled = false' + ' max_concurrent_threads_per_session = 9' + ' max_threads = 4' + ' max_depth = 1' + '' + ) -join "`r`n" + [IO.File]::WriteAllText( + (Join-Path $migrationHome 'config.toml'), + $migrationConfig, + [Text.UTF8Encoding]::new($false) + ) + & ./scripts/Install-TaskAwareAgent.ps1 -CodexHome $migrationHome + $migrated = Get-Content -Raw -LiteralPath (Join-Path $migrationHome 'config.toml') + if ($migrated -match '(?m)^\s*(multi_agent|max_threads|max_depth)\s*=') { + throw 'A legacy multi-agent key remained after migration.' + } + if ([regex]::Matches($migrated, '(?m)^\s*\[agents\]\s*(?:#.*)?$').Count -ne 1) { + throw 'The agents table was duplicated during migration.' + } + if ([regex]::Matches($migrated, '(?m)^\s*enabled\s*=').Count -ne 1) { + throw 'The enabled key was duplicated during migration.' + } + if ([regex]::Matches($migrated, '(?m)^\s*max_concurrent_threads_per_session\s*=').Count -ne 1) { + throw 'The thread limit key was duplicated during migration.' + } + & ./scripts/Test-TaskAwareAgent.ps1 -CodexHome $migrationHome -ConfigOnlyRuntime + + $conflictHome = Join-Path $env:RUNNER_TEMP 'task-aware-conflict' + New-Item -ItemType Directory -Force -Path $conflictHome | Out-Null + 'default_permissions = ":workspace"' | + Set-Content -LiteralPath (Join-Path $conflictHome 'config.toml') -Encoding utf8 + $stopped = $false + try { + & ./scripts/Install-TaskAwareAgent.ps1 ` + -CodexHome $conflictHome -EnableFullAccess -ErrorAction Stop + } + catch { + $stopped = $true + } + if (-not $stopped) { throw 'The permission conflict was not rejected.' } + + $malformedHome = Join-Path $env:RUNNER_TEMP 'task-aware-malformed-markers' + New-Item -ItemType Directory -Force -Path $malformedHome | Out-Null + '' | + Set-Content -LiteralPath (Join-Path $malformedHome 'AGENTS.md') -Encoding utf8 + $stopped = $false + try { + & ./scripts/Install-TaskAwareAgent.ps1 ` + -CodexHome $malformedHome -ErrorAction Stop + } + catch { + $stopped = $true + } + if (-not $stopped) { throw 'Malformed AGENTS.md markers were not rejected.' } + + linux: + name: Linux clean-home round trip + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v7 + + - uses: actions/setup-node@v7 + with: + node-version: 24 + + - name: Install validation runtime + run: | + npm install --global "$CODEX_PACKAGE" + codex --version + + - name: Parse Bash scripts + run: | + bash -n scripts/install-task-aware-agent.sh + bash -n scripts/test-task-aware-agent.sh + shellcheck -S warning \ + scripts/install-task-aware-agent.sh \ + scripts/test-task-aware-agent.sh + + - name: Validate installer behavior + run: | + set -Eeuo pipefail + + clean_home="$RUNNER_TEMP/task-aware-clean" + scripts/install-task-aware-agent.sh --codex-home "$clean_home" + scripts/test-task-aware-agent.sh \ + --codex-home "$clean_home" \ + --config-only-runtime + scripts/install-task-aware-agent.sh --codex-home "$clean_home" + backup_count=$(find "$clean_home/task-aware-backups" -mindepth 1 -maxdepth 1 -type d | wc -l) + if ((backup_count < 2)); then + printf '%s\n' 'Repeated installation reused a backup directory.' >&2 + exit 1 + fi + + migration_home="$RUNNER_TEMP/task-aware-migration" + mkdir -p -- "$migration_home" + cat > "$migration_home/config.toml" <<'EOF' + [features] # legacy feature section + multi_agent = true + + [agents] # preserve this user comment + enabled = false + max_concurrent_threads_per_session = 9 + max_threads = 4 + max_depth = 1 + EOF + scripts/install-task-aware-agent.sh --codex-home "$migration_home" + if grep -Eq '^[[:space:]]*(multi_agent|max_threads|max_depth)[[:space:]]*=' \ + "$migration_home/config.toml"; then + printf '%s\n' 'A legacy multi-agent key remained after migration.' >&2 + exit 1 + fi + if [[ $(grep -Ec '^[[:space:]]*\[agents\][[:space:]]*(#.*)?$' "$migration_home/config.toml") -ne 1 ]]; then + printf '%s\n' 'The agents table was duplicated during migration.' >&2 + exit 1 + fi + if [[ $(grep -Ec '^[[:space:]]*enabled[[:space:]]*=' "$migration_home/config.toml") -ne 1 ]]; then + printf '%s\n' 'The enabled key was duplicated during migration.' >&2 + exit 1 + fi + if [[ $(grep -Ec '^[[:space:]]*max_concurrent_threads_per_session[[:space:]]*=' "$migration_home/config.toml") -ne 1 ]]; then + printf '%s\n' 'The thread limit key was duplicated during migration.' >&2 + exit 1 + fi + scripts/test-task-aware-agent.sh \ + --codex-home "$migration_home" \ + --config-only-runtime + + conflict_home="$RUNNER_TEMP/task-aware-conflict" + mkdir -p -- "$conflict_home" + printf '%s\n' 'default_permissions = ":workspace"' > "$conflict_home/config.toml" + if scripts/install-task-aware-agent.sh \ + --codex-home "$conflict_home" \ + --enable-full-access; then + printf '%s\n' 'The permission conflict was not rejected.' >&2 + exit 1 + fi + + malformed_home="$RUNNER_TEMP/task-aware-malformed-markers" + mkdir -p -- "$malformed_home" + printf '%s\n' '' > "$malformed_home/AGENTS.md" + if scripts/install-task-aware-agent.sh --codex-home "$malformed_home"; then + printf '%s\n' 'Malformed AGENTS.md markers were not rejected.' >&2 + exit 1 + fi diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..782fbbf --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,69 @@ +name: Release + +on: + push: + tags: + - "v*" + +permissions: + contents: read + +jobs: + validation: + uses: ./.github/workflows/ci.yml + + publish: + name: Package and publish + needs: validation + runs-on: ubuntu-latest + permissions: + contents: write + steps: + - uses: actions/checkout@v7 + with: + fetch-depth: 0 + + - name: Validate release tag and metadata + run: | + set -Eeuo pipefail + [[ "$GITHUB_REF_NAME" =~ ^v[0-9]+\.[0-9]+\.[0-9]+([.-][0-9A-Za-z.-]+)?$ ]] + test -f LICENSE + test -f CHANGELOG.md + version="${GITHUB_REF_NAME#v}" + heading_prefix="## [$version] - " + heading=$(grep -F -m1 "$heading_prefix" CHANGELOG.md) + release_date="${heading#"$heading_prefix"}" + [[ "$release_date" =~ ^[0-9]{4}-[0-9]{2}-[0-9]{2}$ ]] + grep -Fqx \ + "[$version]: https://github.com/Eonshore/Codex-Task-Aware-Agent/releases/tag/$GITHUB_REF_NAME" \ + CHANGELOG.md + + - name: Build source archives and checksums + run: | + set -Eeuo pipefail + package="codex-task-aware-agent-${GITHUB_REF_NAME#v}" + mkdir -p dist + git archive \ + --format=zip \ + --prefix="$package/" \ + --output="dist/$package.zip" \ + "$GITHUB_SHA" + git archive \ + --format=tar.gz \ + --prefix="$package/" \ + --output="dist/$package.tar.gz" \ + "$GITHUB_SHA" + cd dist + sha256sum "$package.zip" "$package.tar.gz" > SHA256SUMS + + - name: Create GitHub Release + env: + GH_TOKEN: ${{ github.token }} + run: | + gh release create "$GITHUB_REF_NAME" \ + dist/*.zip \ + dist/*.tar.gz \ + dist/SHA256SUMS \ + --verify-tag \ + --generate-notes \ + --title "Codex Task-Aware Agent $GITHUB_REF_NAME" diff --git a/.gitignore b/.gitignore index 28379be..0445b96 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,5 @@ Thumbs.db *.log task-aware-backups/ +.codex/ +dist/ diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..bb861ae --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,52 @@ +# 変更履歴 + +このプロジェクトは [Keep a Changelog](https://keepachangelog.com/ja/1.1.0/) の形式と [Semantic Versioning](https://semver.org/lang/ja/) に従います。 + +## [Unreleased] + +### 変更 + +- 現行の価格差を踏まえ、Luna Low の D1 を固定入力、明示的な出力契約、客観的完了条件を持つ限定的な読み取り専用調査・検証まで拡張。 +- Terra Medium の D2 を、状態変更を伴う実装、tool-heavy な複数工程、通常判断が必要な調査・検証として明確化。 +- D1 に Luna Max、D2 に Terra Max、D3 に Sol Max の上位 variant を追加。 +- 中間の xhigh role は設けず、標準とMaxの二段階に統一。 +- 能力クラスを先に決め、同じクラス内で標準またはMax枠を選ぶ二段階ルーティングへ変更。 +- 価格低下を、Maxによる完全性向上または手戻り回避を選びやすくする根拠として反映。 +- 最小十分な役割を選びつつ、D0 の細分化、明白な D2/D3 の意図的な過小ルーティング、不要な microtask fan-out を禁止。 +- 価格低下後も、統合負荷と競合を抑えるため同時に開く子スレッドの上限を3つに維持。 + +### 検証 + +- Windows/Linux validator に、価格対応後の D1/D2 境界と過剰委譲防止規則の検査を追加。 +- Windows/Linux installer と validator を六 role の配置、model、effort、sandbox 検査へ拡張し、旧Luna/Terra High roleをbackup後に除去する移行を追加。 +- release 前の live probe を、六 role の model/effort 確認と D0 から D3 までの標準・Maxルーティング確認へ拡張。 + +## [0.1.0] - 2026-07-26 + +初回の公開候補です。 + +### 追加 + +- D0 から D4 までの task-aware delegation policy。 +- Luna Low、Terra Medium、Sol High の custom agent 定義。 +- Windows/Linux の backup 付き installer と validator。 +- Windows/Linux の clean-home CI、週次の最新 Codex 互換性確認。 +- tag から source archive と SHA-256 checksum を作る release workflow。 + +### 変更 + +- 現行 Codex V2 に合わせ、`agents.max_concurrent_threads_per_session = 3` を使用。 +- legacy の `features.multi_agent`、`agents.max_threads`、`agents.max_depth` を導入時に除去。 +- Ultra を reasoning effort ではなく subagent execution mode として説明。 + +### 修正 + +- Windows installer が空の `CODEX_HOME` を初期化できない問題。 +- Windows validator が指定された `CODEX_HOME` 以外を doctor していた問題。 +- `default_permissions` と full-access 設定の競合を見逃す問題。 +- 公開用 policy から `fork_turns = "none"` と task packet の再委譲禁止が欠落していた問題。 +- malformed/duplicate marker による `AGENTS.md` の破損と、同一秒の再導入による Windows backup 衝突。 +- コメント付き TOML table header とインデントされた key を重複生成する問題。 +- release tag と `CHANGELOG.md` の version 不一致を公開前に止めない問題。 + +[0.1.0]: https://github.com/Eonshore/Codex-Task-Aware-Agent/releases/tag/v0.1.0 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..11ff30b --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,31 @@ +# コントリビューション + +issue や pull request を歓迎します。変更は、既存の task routing contract と Windows/Linux の両方を保つ最小単位にしてください。 + +## 開発時の確認 + +Windows では、使い捨ての `CODEX_HOME` を指定して次を実行します。 + +```powershell +pwsh -File .\scripts\Install-TaskAwareAgent.ps1 -CodexHome +pwsh -File .\scripts\Test-TaskAwareAgent.ps1 -CodexHome -ConfigOnlyRuntime +``` + +Linux では次を実行します。 + +```bash +./scripts/install-task-aware-agent.sh --codex-home +./scripts/test-task-aware-agent.sh --codex-home --config-only-runtime +``` + +pull request の前に、PowerShell/Bash の構文確認と `git diff --check` も通してください。 +実際の model、reasoning effort、sandbox の割り当てを変更する場合は、新しい Codex task から custom agent を起動した live probe も記録してください。 + +## 変更時の注意 + +- `.codex/`、認証情報、実ユーザーの `config.toml`、backup を commit しないでください。 +- installer は既存設定を保ち、変更前に backup を作る contract を維持してください。 +- Windows と Linux の挙動を揃え、片方だけの変更を避けてください。 +- security issue は公開 issue ではなく、GitHub Security advisory から報告してください。 + +明示的に別条件を示さない contribution は、プロジェクトと同じ Apache License 2.0 で提供されます。 diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..d645695 --- /dev/null +++ b/LICENSE @@ -0,0 +1,202 @@ + + 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. + + 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 [yyyy] [name of copyright owner] + + 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/README.md b/README.md index 1b84cc1..ddcbe89 100644 --- a/README.md +++ b/README.md @@ -1,35 +1,49 @@ # Codex Task-Aware Agent +[![CI](https://github.com/Eonshore/Codex-Task-Aware-Agent/actions/workflows/ci.yml/badge.svg)](https://github.com/Eonshore/Codex-Task-Aware-Agent/actions/workflows/ci.yml) +[![License](https://img.shields.io/badge/license-Apache--2.0-blue.svg)](LICENSE) + Codex の親エージェントがタスクを難易度別に分類し、必要な場合だけ役割別のカスタムエージェントへ委譲するための設定一式です。 +OpenAI の公式製品ではなく、Codex の公開仕様に基づくコミュニティプロジェクトです。 -この構成が想定する親は、`gpt-5.6-sol` を推論労力 `ultra` で動かす **Sol Ultra** です。 -親は難易度判定、タスクの分割、子の選択、結果の統合を担当し、子には Luna Low、Terra Medium、Sol High を使い分けます。 +この構成が想定する親は、`gpt-5.6-sol` をクライアントの Ultra モードで動かす **Sol Ultra** です。 +親は難易度判定、タスクの分割、子の選択、結果の統合を担当します。 +子には Luna Low/Max、Terra Medium/Max、Sol High/Max を使い分けます。 これにより、すべての子が親の高い推論労力を継承して消費量が膨らむことを避けつつ、メインスレッドへ途中経過が流れ込む量を抑えます。 +現行の価格差は、固定契約の読み取り専用作業を Luna へ寄せ、各難度内で必要な場合に高い推論労力を選ぶ根拠にします。 +安価であることだけを理由に子を細分化はしません。 +各 subagent は独自に token と調整時間を使うため、D0 の直接処理と最大3子の上限は維持します。 リポジトリを取得しただけでは Codex の動作は変わりません。 導入スクリプトを実行し、新しいタスクで設定を読み込む必要があります。 ## 想定する実行構成 -Sol はモデル、Ultra は推論労力と委譲を含む実行設定です。 -Ultra は別のモデル名ではありません。 +Sol はモデル、Ultra は対応するモデルと環境で最大推論と能動的な委譲を利用する実行モードです。 +現行 Codex は、対応モデルの `model_reasoning_effort = "ultra"` も受け付けます。 +このリポジトリでは、親の統合と委譲に Ultra を残し、子の上限を Max にします。 -対応するアカウントとクライアントで Sol Ultra を選ぶと、親は最大の推論労力を使い、分割可能な作業をサブエージェントへ能動的に委譲します。 -このリポジトリは、その委譲に D0 から D4 までの判断基準と、用途別に固定した三つの子エージェントを追加します。 +対応するアカウントとクライアントで Sol Ultra を選ぶと、親は分割可能な作業をサブエージェントへ能動的に委譲します。 +このリポジトリは、その委譲に D0 から D4 までの能力分類と、同じ難度内で推論労力を選ぶ六つの子エージェントを追加します。 | 難易度 | 対象 | 実行役 | モデルと推論労力 | sandbox | | --- | --- | --- | --- | --- | | D0 | 単純で明確な1工程 | 親が直接処理 | Sol Ultra | 親の設定 | -| D1 | 抽出、分類、変換、反復チェック | `luna_task` | Luna Low | read-only | -| D2 | 境界が明確な調査、通常実装、検証 | `terra_worker` | Terra Medium | 親から継承 | -| D3 | 曖昧、高リスク、複数領域、設計判断 | `sol_specialist` | Sol High | read-only | +| D1 標準 | 小さく均質で、固定入力と客観的完了条件がある読み取り専用作業 | `luna_task` | Luna Low | read-only | +| D1 Max枠 | D1 のまま、異種入力、密な突合、coverage 重視、多数の edge case がある作業 | `luna_task_max` | Luna Max | read-only | +| D2 標準 | 状態変更を伴う実装、tool-heavy な複数工程、通常判断が必要な調査・検証 | `terra_worker` | Terra Medium | 親から継承 | +| D2 Max枠 | D2 のまま、制約の結合、長い検証経路、難しいデバッグ、手戻りコストが大きい作業 | `terra_worker_max` | Terra Max | 親から継承 | +| D3 標準 | 一つの難しい判断、曖昧性、高リスク、複数領域、設計判断 | `sol_specialist` | Sol High | read-only | +| D3 Max枠 | 不確実性と結果の重大性がともに高く、証拠競合、不可逆設計、security、敵対的 edge case を含む作業 | `sol_specialist_max` | Sol Max | read-only | | D4 | 独立した D3 タスクが複数 | 親が分割して統合 | Sol Ultra | 親と選択した子の設定 | -`sol_specialist` は難しい判断と検証計画を返す読み取り専用の役割です。 -書き込みを伴う通常実装は、親の権限を継承する `terra_worker` か親が担当します。 -Ultra を使うのは親だけで、D3 の `sol_specialist` も Sol High に抑えています。 -複数の判断をまたぐ推論と最終統合を親へ残し、子ごとに Ultra の推論コストが発生することを避けるためです。 +能力クラスを D1/D2/D3 から先に決め、その後で標準またはMax枠を選びます。 +Max枠を選んでも権限や能力境界は広がりません。 +Luna の二役と Sol の二役は読み取り専用で、書き込みを伴う通常実装は、親の権限を継承する Terra の二役か親が担当します。 +Ultra は親だけに残し、複数の判断をまたぐ推論と最終統合を親が担当します。 +上位枠は三モデルとも Max に統一します。 +`xhigh` は標準とMaxの間に独立した能力境界を作らないため、別roleにはしません。 ## ルーティングの仕組み @@ -43,24 +57,37 @@ Ultra を使うのは親だけで、D3 の `sol_specialist` も Sol High に抑 3. 親のコンテキスト消費か経過時間を減らせる見込みがある。 4. 委譲の調整コストが、親による直接処理より小さい。 +条件を満たした後、親は能力クラスを選び、そのクラス内で必要十分な推論労力を選びます。 +標準 effort が基本ですが、完全性の向上または手戻りの回避が見込める場合は、価格低下を踏まえてMax枠を積極的に選べます。 +Max枠の task packet には、標準 effort では誤りや手戻りが増える具体的な理由を含めます。 +原子的な D0 を Luna が安価であるという理由だけで分割しません。 +同じ入力と完了条件を共有する小さな作業は、分離によって待ち時間、コンテキスト分離、証拠の独立性が改善しない限り、一つの task packet にまとめます。 + 子は別の子を起動しません。 -`max_threads = 4` とポリシー上の上限により、親から同時に使う子は最大3つです。 +`max_concurrent_threads_per_session = 3` とポリシー上の上限により、親から同時に開く子スレッドは最大3つです。 同じファイルや状態を更新するエージェントは1つに限定します。 +子からの再委譲は、agent TOML と `AGENTS.md` の指示で禁止します。 +旧 `agents.max_depth` は Codex V2 で無視されるため、実効的な強制境界としては使用しません。 + `NEEDS_ESCALATION` は Codex ランタイムの自動判定ではなく、子が能力不足の根拠を親へ返すための応答規約です。 -親はその根拠を確認してから、必要な場合だけ上位の役割へ再委譲します。 +合理的に選んだ下位役割が返した場合だけ、親はその根拠を確認し、必要な上位役割へ再委譲します。 +最初から D2 または D3 と明らかな作業を、昇格結果を得るためだけに Luna へ渡しません。 -子を起動するときは、`spawn_agent` の `agent_type` に `luna_task`、`terra_worker`、`sol_specialist` のいずれかを明示します。 +子を起動するときは、`spawn_agent` の `agent_type` に `luna_task`、`luna_task_max`、`terra_worker`、`terra_worker_max`、`sol_specialist`、`sol_specialist_max` のいずれかを明示します。 `task_name` は子タスクの表示名とパスを付ける項目であり、custom agent の選択には使いません。 `task_name = "luna_task"` だけを指定すると、子が親のモデルと推論労力を継承するため、想定したコスト制御になりません。 -D1 から D3 までの委譲では `agent_type` を必須とし、まず必ず引数付きで起動します。 +D1 から D3 までの委譲では、標準とMax枠のどちらでも `agent_type` を必須とし、まず必ず引数付きで起動します。 tool が `agent_type` または custom agent を明示的に拒否した場合だけ、既定の子を起動せず、親で処理して不一致を報告します。 +各 spawn は `fork_turns = "none"` を指定し、親の全会話履歴ではなく task packet だけを子へ渡します。 +task packet 自体にも再委譲禁止を明記します。 ## 前提条件 - Windows では PowerShell 7 以降を使用できること。 - Linux では Bash、`awk`、`grep` を使用できること。 -- カスタムエージェントと multi-agent に対応した Codex を使用していること。 +- カスタムエージェントと subagent workflow に対応した現行 Codex を使用していること。 +- この公開候補の検証基準である Codex CLI 0.145.0 以降を使用すること。 - 使用するアカウントで `gpt-5.6-luna`、`gpt-5.6-terra`、`gpt-5.6-sol` を利用できること。 - Sol Ultra を使う場合は、対応するアカウントとクライアントで Ultra が有効であること。 - コマンドをこのリポジトリのルートで実行すること。 @@ -72,7 +99,7 @@ codex --version codex doctor --summary --no-color --ascii ``` -モデルと推論の選択欄では、三つの子モデルと、親に使う Sol Ultra が表示されることも確認します。 +モデルと推論の選択欄では、三つの子モデル、High/Max を含む必要な effort、親に使う Sol Ultra が表示されることも確認します。 Ultra を利用できない環境でも、Sol xhigh を親にして同じ D0 から D4 までのルーティング規則を使えます。 ただし、それはこのリポジトリが想定する Sol Ultra と同じ実行構成ではありません。 @@ -84,11 +111,14 @@ Ultra を利用できない環境でも、Sol xhigh を親にして同じ D0 か | 対象 | 変更内容 | | --- | --- | -| `config.toml` | `[features] multi_agent = true`、`[agents] max_threads = 4`、`max_depth = 1` を設定 | +| `config.toml` | `[agents] enabled = true`、`max_concurrent_threads_per_session = 3` を設定し、旧 key を除去 | | `AGENTS.md` | マーカーで囲んだ task-aware delegation policy を追加または更新 | | `agents/luna-task.toml` | Luna Low の読み取り専用エージェントを配置 | +| `agents/luna-task-max.toml` | Luna Max の読み取り専用エージェントを配置 | | `agents/terra-worker.toml` | Terra Medium の作業エージェントを配置 | +| `agents/terra-worker-max.toml` | Terra Max の作業エージェントを配置 | | `agents/sol-specialist.toml` | Sol High の読み取り専用エージェントを配置 | +| `agents/sol-specialist-max.toml` | Sol Max の読み取り専用エージェントを配置 | 既存ファイルは、変更前に `$CODEX_HOME/task-aware-backups//` へ退避します。 同名のカスタムエージェントファイルは上書きされます。 @@ -120,8 +150,7 @@ pwsh -File .\scripts\Install-TaskAwareAgent.ps1 -SetSolDefault -EnableFullAccess 別の Codex 環境へ試験導入する場合は `-CodexHome `、変更予定だけを確認する場合は `-WhatIf` を指定できます。 -PowerShell 版は、まだ `config.toml` がない空の `CODEX_HOME` を初期化できません。 -Codex で設定を一度保存するか、有効な TOML を含む `config.toml` を作成してから導入してください。 +PowerShell 版も、`config.toml` がない空の `CODEX_HOME` を初期化できます。 ### Linux @@ -153,25 +182,18 @@ CRLF のまま実行すると、shebang の `bash` を解決できず起動に `-SetSolDefault` と `--set-sol-default` が設定する親の既定値は、`gpt-5.6-sol` と `xhigh` です。 どちらのオプションだけでも Sol Ultra にはなりません。 -想定構成どおりに使う場合は、導入後に対応クライアントのモデルと推論の選択欄で Sol と Ultra を選んでください。 -現在の Codex が `ultra` を設定値として受理する場合は、`config.toml` で次のように指定することもできます。 - -```toml -model = "gpt-5.6-sol" -model_reasoning_effort = "ultra" -``` - -インストーラーが `xhigh` を使うのは、Ultra を設定ファイルから選べないクライアントとの互換性を残すためです。 +想定構成どおりに使う場合は、導入後に対応クライアントのモデル選択で Sol と Ultra を選んでください。 +インストーラーは、アカウントやクライアントごとの Ultra 対応を暗黙に仮定しないため、`model_reasoning_effort = "ultra"` を自動では書き込みません。 +対応を確認できた環境では、クライアントの選択または明示的な設定で親を Ultra にしてください。 ### フルアクセスの影響 `-EnableFullAccess` と `--enable-full-access` は、`approval_policy = "never"` と `sandbox_mode = "danger-full-access"` をグローバル設定へ書き込みます。 -この指定は、親だけでなく sandbox を親から継承する `terra_worker` にも影響します。 +この指定は、親だけでなく sandbox を親から継承する `terra_worker` と `terra_worker_max` にも影響します。 同じ `$CODEX_HOME` を使うほかのプロジェクトにも適用されるため、信頼できる環境でのみ使用してください。 -既存の `config.toml` に `default_permissions` がある場合は、`-EnableFullAccess` または `--enable-full-access` をそのまま使わないでください。 -Codex は `default_permissions` と `sandbox_mode` の併用を想定しておらず、インストーラーも競合を検出または解消しません。 -どちらの権限方式を使うかを決め、既存設定を整理してから導入してください。 +既存の `config.toml` に `default_permissions` がある場合、インストーラーは `-EnableFullAccess` または `--enable-full-access` をエラーで停止します。 +Codex は `default_permissions` と `sandbox_mode` の併用を想定していないため、どちらの権限方式を使うかを決め、既存設定を整理してから再実行してください。 ## 検証 @@ -181,8 +203,9 @@ Codex は `default_permissions` と `sandbox_mode` の併用を想定してお pwsh -File .\scripts\Test-TaskAwareAgent.ps1 ``` -ランタイム確認を省く場合は `-SkipRuntime`、静的検査の対象を変える場合は `-CodexHome ` を指定できます。 -PowerShell 版の `codex doctor` は、`-CodexHome` の値ではなく、実行プロセスが使用している Codex 設定を検査します。 +ランタイム確認を省く場合は `-SkipRuntime`、対象を変える場合は `-CodexHome ` を指定できます。 +認証情報のない clean home や CI では `-ConfigOnlyRuntime` を加えると、strict config load だけを必須にし、認証や接続など別カテゴリの doctor failure を分離できます。 +PowerShell 版も対象の `CODEX_HOME` を明示し、`codex --strict-config doctor --summary` を実行します。 ### Linux @@ -191,18 +214,25 @@ PowerShell 版の `codex doctor` は、`-CodexHome` の値ではなく、実行 ``` ランタイム確認を省く場合は `--skip-runtime`、対象を変える場合は `--codex-home ` を指定できます。 +認証情報のない clean home や CI では `--config-only-runtime` を加えます。 Linux 版は対象の `CODEX_HOME` を明示し、`codex --strict-config doctor --summary` を実行します。 -どちらの検証スクリプトも、配置したファイル、主要な設定値、三つの子のモデル ID、推論労力、宣言した sandbox を静的に確認します。 +どちらの検証スクリプトも、配置したファイル、主要な設定値、六つの子のモデル ID、推論労力、宣言した sandbox を静的に確認します。 `codex` コマンドが見つかる場合は、続けて `codex doctor` を実行します。 この検証は、runtime が子へ適用した実効 sandbox、モデルの利用権限、実際の子の起動、D0 から D4 までの分類結果までは確認しません。 導入後は Codex を再起動するか、新しいタスクを開始し、次の手順で実動作も確認してください。 1. 親のモデルと推論の選択欄が Sol Ultra になっていることを確認する。 -2. 「`agents/*.toml` から name と model を抽出して表にする」のような、完了条件が明確な D1 タスクを依頼する。 -3. 親が D1 と `luna_task` を報告し、`spawn_agent` に `agent_type = "luna_task"` を渡していることを確認する。 -4. 子の詳細を開き、役割、使用モデル、推論労力が Luna Low になっていることを確認する。 +2. 一つのファイルから既知の文字列を読むだけの D0 を依頼し、子を起動しないことを確認する。 +3. 小さく均質で固定契約を持つ読み取り専用 D1 を依頼し、`luna_task` を確認する。 +4. 異種入力の密な突合と coverage 判定を伴うが、客観的に完了判定できる D1 を依頼し、`luna_task_max` を確認する。 +5. 専用の空ディレクトリに一つのファイルを作成して検証する D2 を依頼し、`terra_worker` を確認する。 +6. 複数ファイルの結合制約と長い検証経路を持つ D2 を依頼し、`terra_worker_max` を確認する。 +7. 一つの明確な設計トレードオフを判断する D3 を依頼し、`sol_specialist` を確認する。 +8. 証拠が競合し、不可逆性または security 上の重大性も高い D3 を依頼し、`sol_specialist_max` を確認する。 +9. 親が D0 では spawn せず、D1 から D3 では対応する標準またはMax枠の `agent_type` を渡すことを確認する。 +10. 各子の詳細を開き、Luna Low/Max、Terra Medium/Max、Sol High/Max が実際に適用されていることを確認する。 `AGENTS.md` の指示チェーンは新しい実行の開始時に構築されるため、導入前から開いているタスクでは確認できません。 @@ -216,7 +246,7 @@ Linux 版は対象の `CODEX_HOME` を明示し、`codex --strict-config doctor 導入後に `config.toml` や `AGENTS.md` を変更している場合は、バックアップをそのまま上書きせず、差分を確認して task-aware 関連の設定だけを手動で統合してください。 導入前に存在しなかったファイルはバックアップへ含まれません。 -その場合は、追加された三つのエージェントファイルと、`AGENTS.md` の `BEGIN CODEX TASK-AWARE AGENT` から `END CODEX TASK-AWARE AGENT` までのブロックを手動で取り除く必要があります。 +その場合は、追加された六つのエージェントファイルと、`AGENTS.md` の `BEGIN CODEX TASK-AWARE AGENT` から `END CODEX TASK-AWARE AGENT` までのブロックを手動で取り除く必要があります。 ## ファイル構成 @@ -228,9 +258,25 @@ Linux 版は対象の `CODEX_HOME` を明示し、`codex --strict-config doctor - `scripts/install-task-aware-agent.sh`:Linux 向けのバックアップ付き導入スクリプト - `scripts/test-task-aware-agent.sh`:Linux 向けの配置と Codex 設定の検証スクリプト +## リリース + +`v*` tag を push すると、GitHub Actions が Windows/Linux の clean-home round trip を再実行し、次の成果物を GitHub Release に作成します。 + +- source archive の `.zip` +- source archive の `.tar.gz` +- 両 archive を検証する `SHA256SUMS` + +公開前の手順と必須確認は [RELEASE_CHECKLIST.md](RELEASE_CHECKLIST.md)、変更履歴は [CHANGELOG.md](CHANGELOG.md) を参照してください。 + +## ライセンス + +Apache License 2.0 です。SPDX identifier は `Apache-2.0` です。全文は [LICENSE](LICENSE) を参照してください。 + ## 設計上の注意 - サブエージェントはメインスレッドのノイズを減らしますが、総トークン量や待ち時間が必ず減るわけではありません。 +- 価格低下は同じ難度内でMax枠を選ぶ閾値を下げますが、必要な能力、書き込み、曖昧さ、リスクより優先しません。 +- Max は応答時間と token 使用量を増やすため、標準 effort で十分な作業には使いません。 - Ultra 自体も分割可能な作業へサブエージェントを使うため、独立した成果がある作業だけに委譲を絞ります。 - モデルが利用できることと、ルーティングが適切であることは静的テストだけでは保証できません。 - ルーティングは親の判断に依存するため、D0 から D4 までの境界は決定的ではありません。 @@ -239,6 +285,9 @@ Linux 版は対象の `CODEX_HOME` を明示し、`codex --strict-config doctor ## 参考資料 - [Models](https://learn.chatgpt.com/docs/models) +- [Codex rate card](https://help.openai.com/en/articles/20001106-codex-rate-card) - [Subagents](https://learn.chatgpt.com/docs/agent-configuration/subagents) - [Configuration Reference](https://learn.chatgpt.com/docs/config-file/config-reference) +- [Configuration Schema](https://developers.openai.com/codex/config-schema.json) - [AGENTS.md](https://learn.chatgpt.com/docs/agent-configuration/agents-md) +- [Codex changelog](https://learn.chatgpt.com/docs/changelog) diff --git a/RELEASE_CHECKLIST.md b/RELEASE_CHECKLIST.md new file mode 100644 index 0000000..f9641fd --- /dev/null +++ b/RELEASE_CHECKLIST.md @@ -0,0 +1,31 @@ +# 公開チェックリスト + +## 公開候補 + +- [ ] `main` が最新の `origin/main` と一致し、working tree に意図しない変更がない。 +- [ ] [Codex changelog](https://learn.chatgpt.com/docs/changelog) で最新版を確認し、`.github/workflows/ci.yml` と README の検証基準を更新する。 +- [ ] [Codex rate card](https://help.openai.com/en/articles/20001106-codex-rate-card) でモデル間の価格差を確認し、ルーティング根拠が現行レートと矛盾しない。 +- [ ] `CHANGELOG.md` の version、日付、release link を確定する。 +- [ ] `LICENSE`、`SECURITY.md`、`CONTRIBUTING.md` が release archive に含まれる。 +- [ ] GitHub Actions の Windows/Linux job が成功する。 +- [ ] 認証済みの新しい Codex task で D0 の no-spawn、D1/D2/D3 の標準・Max条件を実行し、Luna Low/Max、Terra Medium/Max、Sol High/Max の model と reasoning effort を live probe する。 +- [ ] Max枠が能力境界や sandbox を広げず、task packet に昇格理由が含まれることを確認する。 +- [ ] `git diff --check` と秘密情報 scan を通す。 + +## 公開 + +annotated tag を作り、tag だけを push します。例は初回 release です。 + +```shell +git tag -a v0.1.0 -m "Codex Task-Aware Agent v0.1.0" +git push origin v0.1.0 +``` + +tag push 後、Release workflow が validation、archive、checksum、GitHub Release 作成を順に行います。 + +## 公開後 + +- [ ] GitHub Release の zip と tar.gz を取得し、`SHA256SUMS` と一致する。 +- [ ] clean home へ release archive から導入し、validator を再実行する。 +- [ ] repository の public visibility、Security advisory、issue template、default branch protection を確認する。 +- [ ] README badge と release link が公開状態で解決する。 diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..2207e95 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,19 @@ +# セキュリティポリシー + +## 対応バージョン + +最新の GitHub Release だけをサポートします。未公開の branch や古い release で再現する問題は、まず最新版で確認してください。 + +## 脆弱性の報告 + +認証情報の露出、権限設定の意図しない緩和、任意コマンド実行、backup の破損など、セキュリティに関わる問題を公開 issue へ書かないでください。 + +GitHub の Security タブから private vulnerability report または draft security advisory を作成し、次を含めてください。 + +- 影響するバージョンと OS。 +- 最小の再現手順。 +- 想定される影響。 +- 秘密情報を除いたログや PoC。 +- 分かる範囲の回避策。 + +受領後は再現性と影響範囲を確認し、修正と公開時期を報告します。修正が利用可能になるまでは、詳細の公開を控えてください。 diff --git a/agents/luna-task-max.toml b/agents/luna-task-max.toml new file mode 100644 index 0000000..3d1c396 --- /dev/null +++ b/agents/luna-task-max.toml @@ -0,0 +1,22 @@ +name = "luna_task_max" +description = """ +Use for D1 work that remains deterministic, read-only, and objectively +verifiable, but needs dense cross-checking across heterogeneous inputs, +coverage-sensitive validation, or many edge cases where Luna Low would be +materially more error-prone. +Do not use for material judgment, broad investigation, or state changes. +""" +model = "gpt-5.6-luna" +model_reasoning_effort = "max" +sandbox_mode = "read-only" + +developer_instructions = """ +Handle exactly one bounded D1 task. +Use Max reasoning for completeness and cross-checking, not to broaden the task's +capability boundary. +Do not broaden scope or delegate. +Do not modify files or external state, even if the runtime grants broader access. +Return only the requested result and essential evidence. +If state changes, material judgment, or architectural reasoning are required, +return NEEDS_ESCALATION with a one-sentence reason. +""" diff --git a/agents/luna-task.toml b/agents/luna-task.toml index 178fac1..0df4637 100644 --- a/agents/luna-task.toml +++ b/agents/luna-task.toml @@ -1,8 +1,11 @@ name = "luna_task" description = """ -Use only for deterministic, well-scoped extraction, classification, -format conversion, and repetitive checks with an explicit success condition. -Do not use for ambiguous decisions or broad investigation. +Use as the default for compact, homogeneous D1 extraction, classification, +format conversion, repetitive checks, and bounded read-only investigation or +validation with fixed inputs, an explicit output contract, and an explicit +success condition. Prefer luna_task_max when dense cross-checking, coverage, +or numerous edge cases make Low materially more error-prone. +Do not use for material judgment, broad investigation, or state changes. """ model = "gpt-5.6-luna" model_reasoning_effort = "low" @@ -13,6 +16,6 @@ Handle exactly one bounded task. Do not broaden scope or delegate. Do not modify files or external state, even if the runtime grants broader access. Return only the requested result and essential evidence. -If judgment or architectural reasoning is required, return NEEDS_ESCALATION -with a one-sentence reason. +If state changes, material judgment, or architectural reasoning are required, +return NEEDS_ESCALATION with a one-sentence reason. """ diff --git a/agents/sol-specialist-max.toml b/agents/sol-specialist-max.toml new file mode 100644 index 0000000..4530471 --- /dev/null +++ b/agents/sol-specialist-max.toml @@ -0,0 +1,18 @@ +name = "sol_specialist_max" +description = """ +Use for D3 work when both uncertainty and consequence are high, such as +conflicting evidence, security-sensitive trade-offs, irreversible architecture, +adversarial edge cases, or a strong need to reduce reasoning variance. +""" +model = "gpt-5.6-sol" +model_reasoning_effort = "max" +sandbox_mode = "read-only" + +developer_instructions = """ +Resolve one exceptionally demanding D3 decision or evidence lane. +Use Max reasoning to reconcile uncertainty, consequences, and edge cases. +Do not repeat broad exploration already completed by cheaper agents. +Do not delegate. +Do not modify files or external state, even if the runtime grants broader access. +Return a concise recommendation, supporting evidence, risks, and validation plan. +""" diff --git a/agents/sol-specialist.toml b/agents/sol-specialist.toml index 2995c6f..f2d23fd 100644 --- a/agents/sol-specialist.toml +++ b/agents/sol-specialist.toml @@ -1,7 +1,9 @@ name = "sol_specialist" description = """ -Use only for ambiguous, high-risk, cross-system, or architecture-heavy work -requiring substantial judgment, trade-off analysis, or edge-case reasoning. +Use as the default for one bounded D3 ambiguous, high-risk, cross-system, or +architecture-heavy decision requiring substantial judgment or trade-off +analysis. Prefer sol_specialist_max when uncertainty and consequence are both +high. """ model = "gpt-5.6-sol" model_reasoning_effort = "high" diff --git a/agents/terra-worker-max.toml b/agents/terra-worker-max.toml new file mode 100644 index 0000000..2bfdb1b --- /dev/null +++ b/agents/terra-worker-max.toml @@ -0,0 +1,20 @@ +name = "terra_worker_max" +description = """ +Use for D2 work that stays within ordinary engineering judgment but has many +coupled constraints, a long tool or verification chain, difficult debugging, +or expensive rework that justifies deeper reasoning than Terra Medium. +Do not use for unresolved architectural trade-offs, exceptional risk, or D3 +ambiguity. +""" +model = "gpt-5.6-terra" +model_reasoning_effort = "max" + +developer_instructions = """ +Own one independently verifiable D2 work item. +Use Max reasoning for coupled constraints, edge cases, and verification, not to +broaden the task's capability boundary. +Stay within the supplied file and subsystem scope. +Do not spawn subagents. +Return outcome, changed files or evidence, verification, and remaining risk. +Escalate when ambiguity, architectural judgment, or risk materially exceeds D2. +""" diff --git a/agents/terra-worker.toml b/agents/terra-worker.toml index 72b9a99..830c31e 100644 --- a/agents/terra-worker.toml +++ b/agents/terra-worker.toml @@ -1,7 +1,9 @@ name = "terra_worker" description = """ -Use for bounded multi-step investigation, ordinary implementation, -test analysis, or tool-heavy work whose success criteria are already clear. +Use as the default for bounded D2 state-changing implementation, tool-heavy +multi-step work, or investigation and verification that requires ordinary +judgment while keeping clear success criteria. Prefer terra_worker_max when +coupled constraints, difficult debugging, or expensive rework justify Max. """ model = "gpt-5.6-terra" model_reasoning_effort = "medium" diff --git a/config/AGENTS.task-aware.md b/config/AGENTS.task-aware.md index 41f91cc..ceee959 100644 --- a/config/AGENTS.task-aware.md +++ b/config/AGENTS.task-aware.md @@ -6,24 +6,63 @@ verifiable work items and classify each item separately. ### Difficulty routing +Classify capability first, then choose reasoning effort inside that class. +Higher effort never expands a role's permissions or substitutes for a higher +difficulty class. + - D0: Atomic, clear, and executable with one focused tool sequence. Do not spawn a subagent. -- D1: Deterministic extraction, classification, transformation, or repetitive - checking with an explicit output contract. Call `spawn_agent` with - `agent_type = "luna_task"`. -- D2: Bounded multi-step investigation, implementation, or verification with - clear completion criteria. Call `spawn_agent` with - `agent_type = "terra_worker"`. +- D1: Deterministic extraction, classification, transformation, repetitive + checking, or bounded read-only investigation or verification. Inputs, the + output contract, and the success condition must be explicit, and the work + must not require material judgment. Use `luna_task` or `luna_task_max` as + defined under effort routing. +- D2: State-changing implementation, tool-heavy multi-step work, or bounded + investigation and verification that requires ordinary judgment. Completion + criteria must still be clear. Use `terra_worker` or `terra_worker_max` as + defined under effort routing. - D3: Ambiguous, cross-system, high-risk, security-sensitive, or architectural - work requiring trade-off judgment. Call `spawn_agent` with - `agent_type = "sol_specialist"`. + work requiring trade-off judgment. Use `sol_specialist` or + `sol_specialist_max` as defined under effort routing. - D4: Two or more independent D3 work items. Orchestrate them from the root, but keep each child bounded and non-recursive. +### Effort routing + +- D1 default: call `spawn_agent` with `agent_type = "luna_task"` for compact, + homogeneous, deterministic work. +- D1 elevated: call `spawn_agent` with `agent_type = "luna_task_max"` when the + work remains deterministic and read-only but heterogeneous inputs, dense + cross-checking, coverage-sensitive validation, or numerous edge cases make + Low materially more error-prone. +- D2 default: call `spawn_agent` with `agent_type = "terra_worker"` for bounded + implementation or investigation requiring ordinary judgment. +- D2 elevated: call `spawn_agent` with `agent_type = "terra_worker_max"` when + the work remains D2 but coupled constraints, a long tool or verification + chain, difficult debugging, or expensive rework justify deeper reasoning. +- D3 default: call `spawn_agent` with `agent_type = "sol_specialist"` for one + bounded difficult decision or evidence lane. +- D3 elevated: call `spawn_agent` with `agent_type = "sol_specialist_max"` when + both uncertainty and consequence are high, including conflicting evidence, + security-sensitive trade-offs, irreversible architecture, adversarial edge + cases, or a strong need to reduce reasoning variance. + +Use the base effort when it is sufficient. Lower model prices reduce the +threshold for elevated effort when it is likely to improve completeness or +avoid rework, but price and task size alone are not sufficient reasons. +Capability, mutation, ambiguity, and risk determine the difficulty class before +cost is considered. Never substitute an elevated lower-class role for a higher +class. + +Use Max as the single elevated effort for D1-D3. Do not add an xhigh middle lane +unless it gains a distinct routing criterion; otherwise it increases routing +ambiguity without changing the capability boundary. + `task_name` labels the child task; it does not select a custom agent. For every D1-D3 spawn, `agent_type` is mandatory. Never omit it, and never encode the role only in `task_name`. Before calling `spawn_agent`, verify that -the request includes the exact `agent_type` selected above. Always attempt the +the request includes the exact base or elevated `agent_type` selected above. +Always attempt the call with `agent_type`; do not infer that it is unavailable from abbreviated tool documentation. Only if the tool explicitly rejects `agent_type` or the selected custom agent should the parent handle the item and report the runtime @@ -41,9 +80,18 @@ Spawn a subagent only when all of the following are true: Never spawn an agent merely to restate the request, create a generic plan, or duplicate another agent's investigation. +Do not split an atomic D0 item solely because Luna is inexpensive. Combine +adjacent microtasks that share inputs and a success contract when separate +children would not improve elapsed time, context isolation, or evidence +independence. + Use at most three direct children unless the user explicitly requests more. -Keep delegation at one level; children must not spawn descendants. Use only one -writing agent for overlapping files or state. +Every spawn must set `fork_turns = "none"`. +The runtime configuration caps open child threads at three, excluding the +primary thread. Keep delegation at one level; children must not spawn +descendants. Do not rely on `agents.max_depth` for this boundary because Codex +V2 ignores that legacy setting. Use only one writing agent for overlapping +files or state. ### Task packet @@ -55,8 +103,14 @@ Give every child only the minimum task packet required: - completion condition; - required output shape. -Require distilled findings instead of raw logs. Escalate to a stronger role -only after the cheaper role returns `NEEDS_ESCALATION` with concrete evidence. +When selecting an elevated effort variant, also include the concrete reason the +base effort is likely to be materially more error-prone or expensive to rework. + +The packet must explicitly tell the child not to delegate. +Require distilled findings instead of raw logs. If a reasonably selected +lower-cost role returns `NEEDS_ESCALATION`, escalate only with concrete +evidence. Do not route an obvious D2 or D3 item through a cheaper role merely +to obtain an escalation result. After a spawn succeeds, the parent must not perform the same assigned work in parallel. Wait for the child and limit parent-side checks to validating the returned evidence and integrating the result. diff --git a/config/config.task-aware.toml b/config/config.task-aware.toml index 211a158..5f046b8 100644 --- a/config/config.task-aware.toml +++ b/config/config.task-aware.toml @@ -1,8 +1,4 @@ -# Merge these sections into ~/.codex/config.toml. - -[features] -multi_agent = true - +# Merge this section into ~/.codex/config.toml. [agents] -max_threads = 4 -max_depth = 1 +enabled = true +max_concurrent_threads_per_session = 3 diff --git a/scripts/Install-TaskAwareAgent.ps1 b/scripts/Install-TaskAwareAgent.ps1 index 39c25b3..e1ef56b 100644 --- a/scripts/Install-TaskAwareAgent.ps1 +++ b/scripts/Install-TaskAwareAgent.ps1 @@ -18,6 +18,12 @@ $AgentsPath = Join-Path $CodexHome 'agents' $AgentsMdPath = Join-Path $CodexHome 'AGENTS.md' $Timestamp = Get-Date -Format 'yyyyMMdd-HHmmss' $BackupPath = Join-Path $CodexHome "task-aware-backups/$Timestamp" +$BackupSuffix = 0 + +while (Test-Path -LiteralPath $BackupPath) { + $BackupSuffix++ + $BackupPath = Join-Path $CodexHome "task-aware-backups/$Timestamp-$BackupSuffix" +} function Backup-IfPresent { param([Parameter(Mandatory)][string]$Path) @@ -33,13 +39,13 @@ function Backup-IfPresent { function Set-TomlSectionValues { param( - [Parameter(Mandatory)][string]$Content, + [Parameter(Mandatory)][AllowEmptyString()][string]$Content, [Parameter(Mandatory)][string]$Section, [Parameter(Mandatory)][System.Collections.Specialized.OrderedDictionary]$Values ) $escapedSection = [regex]::Escape($Section) - $headerPattern = "(?m)^\[$escapedSection\]\s*$" + $headerPattern = "(?m)^[ \t]*\[$escapedSection\][ \t]*(?:#[^\r\n]*)?\r?$" $header = [regex]::Match($Content, $headerPattern) if (-not $header.Success) { @@ -52,13 +58,13 @@ function Set-TomlSectionValues { $bodyStart = $header.Index + $header.Length $remaining = $Content.Substring($bodyStart) - $nextHeader = [regex]::Match($remaining, '(?m)^\[[^\r\n]+\]\s*$') + $nextHeader = [regex]::Match($remaining, '(?m)^[ \t]*\[[^\]\r\n]+\][ \t]*(?:#[^\r\n]*)?\r?$') $bodyLength = if ($nextHeader.Success) { $nextHeader.Index } else { $remaining.Length } $body = $remaining.Substring(0, $bodyLength) foreach ($entry in $Values.GetEnumerator()) { $escapedKey = [regex]::Escape([string]$entry.Key) - $keyPattern = "(?m)^$escapedKey\s*=.*$" + $keyPattern = "(?m)^[ \t]*$escapedKey[ \t]*=.*$" $replacement = "$($entry.Key) = $($entry.Value)" if ([regex]::IsMatch($body, $keyPattern)) { $body = [regex]::Replace($body, $keyPattern, $replacement, 1) @@ -71,19 +77,49 @@ function Set-TomlSectionValues { return $Content.Substring(0, $bodyStart) + $body + $remaining.Substring($bodyLength) } +function Remove-TomlSectionKeys { + param( + [Parameter(Mandatory)][AllowEmptyString()][string]$Content, + [Parameter(Mandatory)][string]$Section, + [Parameter(Mandatory)][string[]]$Keys + ) + + $escapedSection = [regex]::Escape($Section) + $headerPattern = "(?m)^[ \t]*\[$escapedSection\][ \t]*(?:#[^\r\n]*)?\r?$" + $header = [regex]::Match($Content, $headerPattern) + if (-not $header.Success) { return $Content } + + $bodyStart = $header.Index + $header.Length + $remaining = $Content.Substring($bodyStart) + $nextHeader = [regex]::Match($remaining, '(?m)^[ \t]*\[[^\]\r\n]+\][ \t]*(?:#[^\r\n]*)?\r?$') + $bodyLength = if ($nextHeader.Success) { $nextHeader.Index } else { $remaining.Length } + $body = $remaining.Substring(0, $bodyLength) + + foreach ($key in $Keys) { + $escapedKey = [regex]::Escape($key) + $body = [regex]::Replace( + $body, + "(?m)^[ \t]*$escapedKey[ \t]*=.*(?:\r?\n|$)", + '' + ) + } + + return $Content.Substring(0, $bodyStart) + $body + $remaining.Substring($bodyLength) +} + function Set-TopLevelTomlValue { param( - [Parameter(Mandatory)][string]$Content, + [Parameter(Mandatory)][AllowEmptyString()][string]$Content, [Parameter(Mandatory)][string]$Key, [Parameter(Mandatory)][string]$Value ) - $firstSection = [regex]::Match($Content, '(?m)^\[[^\r\n]+\]\s*$') + $firstSection = [regex]::Match($Content, '(?m)^[ \t]*\[[^\]\r\n]+\][ \t]*(?:#[^\r\n]*)?\r?$') $headLength = if ($firstSection.Success) { $firstSection.Index } else { $Content.Length } $head = $Content.Substring(0, $headLength) $tail = $Content.Substring($headLength) $escapedKey = [regex]::Escape($Key) - $keyPattern = "(?m)^$escapedKey\s*=.*$" + $keyPattern = "(?m)^[ \t]*$escapedKey[ \t]*=.*$" $replacement = "$Key = $Value" if ([regex]::IsMatch($head, $keyPattern)) { @@ -96,6 +132,44 @@ function Set-TopLevelTomlValue { return $head + $tail } +$expectedAgentFiles = @( + 'luna-task.toml', + 'luna-task-max.toml', + 'terra-worker.toml', + 'terra-worker-max.toml', + 'sol-specialist.toml', + 'sol-specialist-max.toml' +) +$retiredAgentFiles = @('luna-task-high.toml', 'terra-worker-high.toml') +if (-not (Test-Path -LiteralPath $PolicySource -PathType Leaf)) { + throw "Missing policy source: $PolicySource" +} +foreach ($agentFile in $expectedAgentFiles) { + $agentSourcePath = Join-Path $AgentsSource $agentFile + if (-not (Test-Path -LiteralPath $agentSourcePath -PathType Leaf)) { + throw "Missing agent source: $agentSourcePath" + } +} + +if ($EnableFullAccess -and (Test-Path -LiteralPath $ConfigPath)) { + $existingConfig = Get-Content -Raw -LiteralPath $ConfigPath + if ($existingConfig -match '(?m)^[ \t]*default_permissions[ \t]*=') { + throw 'Cannot use -EnableFullAccess while config.toml defines default_permissions. Remove one permission system before retrying.' + } +} + +if (Test-Path -LiteralPath $AgentsMdPath) { + $existingAgentsMd = Get-Content -Raw -LiteralPath $AgentsMdPath + $beginMarker = '' + $endMarker = '' + $beginCount = [regex]::Matches($existingAgentsMd, [regex]::Escape($beginMarker)).Count + $endCount = [regex]::Matches($existingAgentsMd, [regex]::Escape($endMarker)).Count + $completeBlock = "(?s)" + [regex]::Escape($beginMarker) + ".*?" + [regex]::Escape($endMarker) + if ($beginCount -ne $endCount -or $beginCount -gt 1 -or ($beginCount -eq 1 -and $existingAgentsMd -notmatch $completeBlock)) { + throw 'AGENTS.md contains malformed or duplicate Task-Aware Agent markers. Repair the marker block before retrying.' + } +} + if (-not $PSCmdlet.ShouldProcess($CodexHome, 'Install Codex Task-Aware Agent configuration')) { return } @@ -104,8 +178,11 @@ New-Item -ItemType Directory -Force -Path $CodexHome, $AgentsPath, $BackupPath | Backup-IfPresent -Path $ConfigPath Backup-IfPresent -Path $AgentsMdPath -Get-ChildItem -LiteralPath $AgentsSource -Filter '*.toml' | ForEach-Object { - Backup-IfPresent -Path (Join-Path $AgentsPath $_.Name) +foreach ($agentFile in $expectedAgentFiles) { + Backup-IfPresent -Path (Join-Path $AgentsPath $agentFile) +} +foreach ($agentFile in $retiredAgentFiles) { + Backup-IfPresent -Path (Join-Path $AgentsPath $agentFile) } $config = if (Test-Path -LiteralPath $ConfigPath) { @@ -113,13 +190,17 @@ $config = if (Test-Path -LiteralPath $ConfigPath) { } else { '' } -$config = Set-TomlSectionValues -Content $config -Section 'features' -Values ([ordered]@{ - multi_agent = 'true' -}) $config = Set-TomlSectionValues -Content $config -Section 'agents' -Values ([ordered]@{ - max_threads = '4' - max_depth = '1' + enabled = 'true' + max_concurrent_threads_per_session = '3' }) +$config = Remove-TomlSectionKeys -Content $config -Section 'agents' -Keys @( + 'max_threads', + 'max_depth' +) +$config = Remove-TomlSectionKeys -Content $config -Section 'features' -Keys @( + 'multi_agent' +) if ($SetSolDefault) { $config = Set-TopLevelTomlValue -Content $config -Key 'model' -Value '"gpt-5.6-sol"' @@ -150,7 +231,15 @@ else { } Set-Content -LiteralPath $AgentsMdPath -Value $agentsMd.TrimStart() -Encoding utf8 -Copy-Item -Path (Join-Path $AgentsSource '*.toml') -Destination $AgentsPath -Force +foreach ($agentFile in $expectedAgentFiles) { + Copy-Item -LiteralPath (Join-Path $AgentsSource $agentFile) -Destination $AgentsPath -Force +} +foreach ($agentFile in $retiredAgentFiles) { + $retiredPath = Join-Path $AgentsPath $agentFile + if (Test-Path -LiteralPath $retiredPath -PathType Leaf) { + Remove-Item -LiteralPath $retiredPath -Force + } +} Write-Host "Installed Task-Aware Agent configuration in $CodexHome" Write-Host "Backup: $BackupPath" diff --git a/scripts/Test-TaskAwareAgent.ps1 b/scripts/Test-TaskAwareAgent.ps1 index 24c07b5..6c9a6e4 100644 --- a/scripts/Test-TaskAwareAgent.ps1 +++ b/scripts/Test-TaskAwareAgent.ps1 @@ -4,7 +4,8 @@ param( if ($env:CODEX_HOME) { $env:CODEX_HOME } else { Join-Path $HOME '.codex' } ), - [switch]$SkipRuntime + [switch]$SkipRuntime, + [switch]$ConfigOnlyRuntime ) $ErrorActionPreference = 'Stop' @@ -29,38 +30,66 @@ function Assert-FileContains { } } +function Assert-FileAbsent { + param([Parameter(Mandatory)][string]$Path) + + if (Test-Path -LiteralPath $Path) { + $failures.Add("Unexpected retired file: $Path") + } +} + $configPath = Join-Path $CodexHome 'config.toml' $agentsMdPath = Join-Path $CodexHome 'AGENTS.md' $agentsPath = Join-Path $CodexHome 'agents' Assert-FileContains -Path $configPath -Patterns @( - '(?m)^\[features\]\s*$', - '(?m)^multi_agent\s*=\s*true\s*$', - '(?m)^\[agents\]\s*$', - '(?m)^max_threads\s*=\s*4\s*$', - '(?m)^max_depth\s*=\s*1\s*$' + '(?m)^[ \t]*\[agents\][ \t]*(?:#[^\r\n]*)?\r?$', + '(?m)^enabled\s*=\s*true\s*$', + '(?m)^max_concurrent_threads_per_session\s*=\s*3\s*$' ) Assert-FileContains -Path $agentsMdPath -Patterns @( '', 'Task-aware delegation policy', 'agent_type\s*=\s*"luna_task"', + 'agent_type\s*=\s*"luna_task_max"', 'agent_type\s*=\s*"terra_worker"', + 'agent_type\s*=\s*"terra_worker_max"', 'agent_type\s*=\s*"sol_specialist"', + 'agent_type\s*=\s*"sol_specialist_max"', + 'Classify capability first, then choose reasoning effort', + 'Higher effort never expands a role''s permissions', + 'Lower model prices reduce the\s+threshold for elevated effort', + 'Use Max as the single elevated effort for D1-D3', + 'Do not add an xhigh middle lane', + 'concrete reason the\s+base effort is likely to be materially more error-prone', + 'bounded read-only investigation or verification', + 'Inputs, the\s+output contract, and the success condition must be explicit', + 'State-changing implementation', + 'tool-heavy multi-step work', + 'requires ordinary judgment', + 'Do not split an atomic D0 item solely because Luna is inexpensive', + 'Do not route an obvious D2 or D3 item through a cheaper role', + 'fork_turns\s*=\s*"none"', + 'packet must explicitly tell the child not to delegate', '' ) $expectedAgents = [ordered]@{ - 'luna-task.toml' = [ordered]@{ Model = 'gpt-5.6-luna'; Effort = 'low'; Sandbox = 'read-only' } - 'terra-worker.toml' = [ordered]@{ Model = 'gpt-5.6-terra'; Effort = 'medium'; Sandbox = $null } - 'sol-specialist.toml' = [ordered]@{ Model = 'gpt-5.6-sol'; Effort = 'high'; Sandbox = 'read-only' } + 'luna-task.toml' = [ordered]@{ Name = 'luna_task'; Model = 'gpt-5.6-luna'; Effort = 'low'; Sandbox = 'read-only' } + 'luna-task-max.toml' = [ordered]@{ Name = 'luna_task_max'; Model = 'gpt-5.6-luna'; Effort = 'max'; Sandbox = 'read-only' } + 'terra-worker.toml' = [ordered]@{ Name = 'terra_worker'; Model = 'gpt-5.6-terra'; Effort = 'medium'; Sandbox = $null } + 'terra-worker-max.toml' = [ordered]@{ Name = 'terra_worker_max'; Model = 'gpt-5.6-terra'; Effort = 'max'; Sandbox = $null } + 'sol-specialist.toml' = [ordered]@{ Name = 'sol_specialist'; Model = 'gpt-5.6-sol'; Effort = 'high'; Sandbox = 'read-only' } + 'sol-specialist-max.toml' = [ordered]@{ Name = 'sol_specialist_max'; Model = 'gpt-5.6-sol'; Effort = 'max'; Sandbox = 'read-only' } } foreach ($entry in $expectedAgents.GetEnumerator()) { $escapedModel = [regex]::Escape([string]$entry.Value.Model) $escapedEffort = [regex]::Escape([string]$entry.Value.Effort) + $escapedName = [regex]::Escape([string]$entry.Value.Name) $patterns = @( - '(?m)^name\s*=\s*"[^\"]+"\s*$', + "(?m)^name\s*=\s*`"$escapedName`"\s*$", '(?m)^description\s*=\s*"""', '(?m)^developer_instructions\s*=\s*"""', "(?m)^model\s*=\s*`"$escapedModel`"\s*$", @@ -70,9 +99,47 @@ foreach ($entry in $expectedAgents.GetEnumerator()) { $escapedSandbox = [regex]::Escape([string]$entry.Value.Sandbox) $patterns += "(?m)^sandbox_mode\s*=\s*`"$escapedSandbox`"\s*$" } + if ($entry.Key -eq 'luna-task.toml') { + $patterns += 'Use as the default for compact, homogeneous D1' + $patterns += 'bounded read-only investigation or' + $patterns += 'fixed inputs, an explicit output contract' + $patterns += 'success condition' + $patterns += 'Do not use for material judgment, broad investigation, or state changes' + } + elseif ($entry.Key -eq 'luna-task-max.toml') { + $patterns += 'D1 work that remains deterministic, read-only, and objectively' + $patterns += 'dense cross-checking across heterogeneous inputs' + $patterns += 'Do not use for material judgment, broad investigation, or state changes' + $patterns += 'Use Max reasoning for completeness and cross-checking' + $patterns += 'not to broaden the task''s\s+capability boundary' + } + elseif ($entry.Key -eq 'terra-worker.toml') { + $patterns += 'Use as the default for bounded D2 state-changing implementation' + $patterns += 'tool-heavy\s+multi-step work' + $patterns += 'requires\s+ordinary\s+judgment' + } + elseif ($entry.Key -eq 'terra-worker-max.toml') { + $patterns += 'D2 work that stays within ordinary engineering judgment' + $patterns += 'many\s+coupled constraints' + $patterns += 'Do not use for unresolved architectural trade-offs' + $patterns += 'Use Max reasoning for coupled constraints, edge cases, and verification' + $patterns += 'not to\s+broaden the task''s capability boundary' + } + elseif ($entry.Key -eq 'sol-specialist-max.toml') { + $patterns += 'D3 work when both uncertainty and consequence are high' + $patterns += 'security-sensitive trade-offs' + $patterns += 'reasoning variance' + } + elseif ($entry.Key -eq 'sol-specialist.toml') { + $patterns += 'Use as the default for one bounded D3' + $patterns += 'Prefer sol_specialist_max when uncertainty and consequence are both' + } Assert-FileContains -Path (Join-Path $agentsPath $entry.Key) -Patterns $patterns } +Assert-FileAbsent -Path (Join-Path $agentsPath 'luna-task-high.toml') +Assert-FileAbsent -Path (Join-Path $agentsPath 'terra-worker-high.toml') + if ($failures.Count -gt 0) { $failures | ForEach-Object { Write-Error $_ } exit 1 @@ -80,13 +147,44 @@ if ($failures.Count -gt 0) { $codex = Get-Command codex -ErrorAction SilentlyContinue if ($codex -and -not $SkipRuntime) { - & $codex.Source doctor --summary --no-color --ascii - if ($LASTEXITCODE -ne 0) { - throw "codex doctor failed with exit code $LASTEXITCODE" + $previousCodexHome = $env:CODEX_HOME + try { + $env:CODEX_HOME = [IO.Path]::GetFullPath($CodexHome) + if ($ConfigOnlyRuntime) { + $doctorOutput = (& $codex.Source --strict-config doctor --json --no-color | Out-String) + $doctorExitCode = $LASTEXITCODE + try { + $doctorReport = $doctorOutput | ConvertFrom-Json -Depth 20 + } + catch { + throw "codex doctor did not return valid JSON for CODEX_HOME=$($env:CODEX_HOME): $($doctorOutput.Trim())" + } + + $configCheck = $doctorReport.checks.'config.load' + if (-not $configCheck -or $configCheck.status -ne 'ok') { + throw "Codex strict config load failed for CODEX_HOME=$($env:CODEX_HOME) (doctor exit $doctorExitCode)." + } + Write-Host "Codex strict config load passed for CODEX_HOME=$($env:CODEX_HOME)." + } + else { + & $codex.Source --strict-config doctor --summary --no-color --ascii + if ($LASTEXITCODE -ne 0) { + throw "codex doctor failed with exit code $LASTEXITCODE for CODEX_HOME=$($env:CODEX_HOME)" + } + } + } + finally { + if ($null -eq $previousCodexHome) { + Remove-Item Env:CODEX_HOME -ErrorAction SilentlyContinue + } + else { + $env:CODEX_HOME = $previousCodexHome + } } } elseif (-not $codex -and -not $SkipRuntime) { Write-Warning 'codex was not found; file validation passed but runtime validation was skipped.' } +$global:LASTEXITCODE = 0 Write-Host 'Task-Aware Agent validation passed.' diff --git a/scripts/install-task-aware-agent.sh b/scripts/install-task-aware-agent.sh index 9514c99..41b2c9b 100755 --- a/scripts/install-task-aware-agent.sh +++ b/scripts/install-task-aware-agent.sh @@ -70,6 +70,44 @@ done [[ -f "$policy_source" ]] || die "missing policy file: $policy_source" command -v awk >/dev/null || die 'awk is required' +expected_agent_files=( + luna-task.toml + luna-task-max.toml + terra-worker.toml + terra-worker-max.toml + sol-specialist.toml + sol-specialist-max.toml +) +retired_agent_files=(luna-task-high.toml terra-worker-high.toml) +for agent_file in "${expected_agent_files[@]}"; do + [[ -f "$agents_source/$agent_file" ]] || die "missing agent file: $agents_source/$agent_file" +done + +validate_policy_markers() { + local path=$1 + + awk ' + BEGIN { + begin_marker = "" + end_marker = "" + } + + $0 == begin_marker { + begin_count++ + if (begin_count > 1 || end_count > 0) invalid = 1 + } + + $0 == end_marker { + end_count++ + if (begin_count != 1 || end_count > 1) invalid = 1 + } + + END { + if (invalid || begin_count != end_count || begin_count > 1) exit 1 + } + ' "$path" +} + backup_if_present() { local source=$1 local relative destination @@ -105,14 +143,14 @@ set_toml_section_value() { } } - $0 ~ "^[[:space:]]*\\[" section "\\][[:space:]]*$" { + $0 ~ "^[[:space:]]*\\[" section "\\][[:space:]]*(#.*)?$" { section_found = 1 in_section = 1 print next } - in_section && $0 ~ "^[[:space:]]*\\[[^]]+\\][[:space:]]*$" { + in_section && $0 ~ "^[[:space:]]*\\[[^]]+\\][[:space:]]*(#.*)?$" { emit_value() in_section = 0 } @@ -158,7 +196,7 @@ set_top_level_toml_value() { next } - before_section && $0 ~ "^[[:space:]]*\\[[^]]+\\][[:space:]]*$" { + before_section && $0 ~ "^[[:space:]]*\\[[^]]+\\][[:space:]]*(#.*)?$" { if (!key_written) { print key " = " value print "" @@ -179,6 +217,30 @@ set_top_level_toml_value() { replace_file "$temporary" "$path" } +remove_toml_section_key() { + local path=$1 + local section=$2 + local key=$3 + local temporary + + temporary=$(mktemp "$codex_home/.task-aware-config.XXXXXX") + awk -v section="$section" -v key="$key" ' + $0 ~ "^[[:space:]]*\\[" section "\\][[:space:]]*(#.*)?$" { + in_section = 1 + print + next + } + + in_section && $0 ~ "^[[:space:]]*\\[[^]]+\\][[:space:]]*(#.*)?$" { + in_section = 0 + } + + in_section && $0 ~ "^[[:space:]]*" key "[[:space:]]*=" { next } + { print } + ' "$path" > "$temporary" + replace_file "$temporary" "$path" +} + merge_policy_block() { local destination=$1 local temporary @@ -225,19 +287,33 @@ merge_policy_block() { replace_file "$temporary" "$destination" } +if [[ -f "$agents_md_path" ]] && ! validate_policy_markers "$agents_md_path"; then + die 'AGENTS.md contains malformed or duplicate Task-Aware Agent markers; repair the marker block before retrying' +fi + +if [[ "$enable_full_access" == true && -f "$config_path" ]] && + grep -Eq '^[[:space:]]*default_permissions[[:space:]]*=' "$config_path"; then + die 'cannot use --enable-full-access while config.toml defines default_permissions; remove one permission system before retrying' +fi + mkdir -p -- "$codex_home" "$agents_path" "$backup_path" backup_if_present "$config_path" backup_if_present "$agents_md_path" -while IFS= read -r agent_file; do - backup_if_present "$agents_path/$(basename -- "$agent_file")" -done < <(find "$agents_source" -maxdepth 1 -type f -name '*.toml' -print | sort) +for agent_file in "${expected_agent_files[@]}"; do + backup_if_present "$agents_path/$agent_file" +done +for agent_file in "${retired_agent_files[@]}"; do + backup_if_present "$agents_path/$agent_file" +done touch -- "$config_path" "$agents_md_path" -set_toml_section_value "$config_path" features multi_agent true -set_toml_section_value "$config_path" agents max_threads 4 -set_toml_section_value "$config_path" agents max_depth 1 +set_toml_section_value "$config_path" agents enabled true +set_toml_section_value "$config_path" agents max_concurrent_threads_per_session 3 +remove_toml_section_key "$config_path" agents max_threads +remove_toml_section_key "$config_path" agents max_depth +remove_toml_section_key "$config_path" features multi_agent if [[ "$set_sol_default" == true ]]; then set_top_level_toml_value "$config_path" model '"gpt-5.6-sol"' @@ -250,7 +326,12 @@ if [[ "$enable_full_access" == true ]]; then fi merge_policy_block "$agents_md_path" -cp -f -- "$agents_source"/*.toml "$agents_path/" +for agent_file in "${expected_agent_files[@]}"; do + cp -f -- "$agents_source/$agent_file" "$agents_path/$agent_file" +done +for agent_file in "${retired_agent_files[@]}"; do + rm -f -- "$agents_path/$agent_file" +done printf 'Installed Task-Aware Agent configuration in %s\n' "$codex_home" printf 'Backup: %s\n' "$backup_path" diff --git a/scripts/test-task-aware-agent.sh b/scripts/test-task-aware-agent.sh index e514718..1061e09 100755 --- a/scripts/test-task-aware-agent.sh +++ b/scripts/test-task-aware-agent.sh @@ -11,12 +11,15 @@ Validate an installed Codex Task-Aware Agent configuration. Options: --codex-home PATH Target Codex home (default: $CODEX_HOME or ~/.codex) --skip-runtime Skip the codex doctor runtime check + --config-only-runtime + Require strict config loading but ignore unrelated doctor failures -h, --help Show this help EOF } codex_home="${CODEX_HOME:-$HOME/.codex}" skip_runtime=false +config_only_runtime=false while (($# > 0)); do case "$1" in @@ -32,6 +35,10 @@ while (($# > 0)); do skip_runtime=true shift ;; + --config-only-runtime) + config_only_runtime=true + shift + ;; -h|--help) usage exit 0 @@ -64,48 +71,130 @@ assert_file_contains() { done } +assert_file_absent() { + local path=$1 + + if [[ -e "$path" ]]; then + printf 'Unexpected retired file: %s\n' "$path" >&2 + failures=$((failures + 1)) + fi +} + config_path="$codex_home/config.toml" agents_md_path="$codex_home/AGENTS.md" agents_path="$codex_home/agents" assert_file_contains "$config_path" \ - '^\[features\][[:space:]]*$' \ - '^multi_agent[[:space:]]*=[[:space:]]*true[[:space:]]*$' \ - '^\[agents\][[:space:]]*$' \ - '^max_threads[[:space:]]*=[[:space:]]*4[[:space:]]*$' \ - '^max_depth[[:space:]]*=[[:space:]]*1[[:space:]]*$' + '^[[:space:]]*\[agents\][[:space:]]*(#.*)?$' \ + '^enabled[[:space:]]*=[[:space:]]*true[[:space:]]*$' \ + '^max_concurrent_threads_per_session[[:space:]]*=[[:space:]]*3[[:space:]]*$' assert_file_contains "$agents_md_path" \ '' \ 'Task-aware delegation policy' \ 'agent_type[[:space:]]*=[[:space:]]*"luna_task"' \ + 'agent_type[[:space:]]*=[[:space:]]*"luna_task_max"' \ 'agent_type[[:space:]]*=[[:space:]]*"terra_worker"' \ + 'agent_type[[:space:]]*=[[:space:]]*"terra_worker_max"' \ 'agent_type[[:space:]]*=[[:space:]]*"sol_specialist"' \ + 'agent_type[[:space:]]*=[[:space:]]*"sol_specialist_max"' \ + 'Classify capability first, then choose reasoning effort' \ + "Higher effort never expands a role's permissions" \ + 'Lower model prices reduce the' \ + 'threshold for elevated effort' \ + 'Use Max as the single elevated effort for D1-D3' \ + 'Do not add an xhigh middle lane' \ + 'concrete reason the' \ + 'base effort is likely to be materially more error-prone' \ + 'bounded read-only investigation or verification' \ + 'Inputs, the' \ + 'output contract, and the success condition must be explicit' \ + 'State-changing implementation' \ + 'tool-heavy multi-step work' \ + 'requires ordinary judgment' \ + 'Do not split an atomic D0 item solely because Luna is inexpensive' \ + 'Do not route an obvious D2 or D3 item through a cheaper role' \ + 'fork_turns[[:space:]]*=[[:space:]]*"none"' \ + 'packet must explicitly tell the child not to delegate' \ '' assert_file_contains "$agents_path/luna-task.toml" \ - '^name[[:space:]]*=[[:space:]]*"[^"]+"[[:space:]]*$' \ + '^name[[:space:]]*=[[:space:]]*"luna_task"[[:space:]]*$' \ '^description[[:space:]]*=[[:space:]]*"""' \ '^developer_instructions[[:space:]]*=[[:space:]]*"""' \ '^model[[:space:]]*=[[:space:]]*"gpt-5\.6-luna"[[:space:]]*$' \ '^model_reasoning_effort[[:space:]]*=[[:space:]]*"low"[[:space:]]*$' \ - '^sandbox_mode[[:space:]]*=[[:space:]]*"read-only"[[:space:]]*$' + '^sandbox_mode[[:space:]]*=[[:space:]]*"read-only"[[:space:]]*$' \ + 'Use as the default for compact, homogeneous D1' \ + 'bounded read-only investigation or' \ + 'fixed inputs, an explicit output contract' \ + 'success condition' \ + 'Do not use for material judgment, broad investigation, or state changes' + +assert_file_contains "$agents_path/luna-task-max.toml" \ + '^name[[:space:]]*=[[:space:]]*"luna_task_max"[[:space:]]*$' \ + '^description[[:space:]]*=[[:space:]]*"""' \ + '^developer_instructions[[:space:]]*=[[:space:]]*"""' \ + '^model[[:space:]]*=[[:space:]]*"gpt-5\.6-luna"[[:space:]]*$' \ + '^model_reasoning_effort[[:space:]]*=[[:space:]]*"max"[[:space:]]*$' \ + '^sandbox_mode[[:space:]]*=[[:space:]]*"read-only"[[:space:]]*$' \ + 'D1 work that remains deterministic, read-only, and objectively' \ + 'dense cross-checking across heterogeneous inputs' \ + 'Do not use for material judgment, broad investigation, or state changes' \ + 'Use Max reasoning for completeness and cross-checking' \ + "not to broaden the task's" \ + 'capability boundary' assert_file_contains "$agents_path/terra-worker.toml" \ - '^name[[:space:]]*=[[:space:]]*"[^"]+"[[:space:]]*$' \ + '^name[[:space:]]*=[[:space:]]*"terra_worker"[[:space:]]*$' \ '^description[[:space:]]*=[[:space:]]*"""' \ '^developer_instructions[[:space:]]*=[[:space:]]*"""' \ + 'Use as the default for bounded D2 state-changing implementation' \ + 'tool-heavy' \ + 'multi-step work' \ + 'requires ordinary' \ + 'judgment while keeping clear success criteria' \ '^model[[:space:]]*=[[:space:]]*"gpt-5\.6-terra"[[:space:]]*$' \ '^model_reasoning_effort[[:space:]]*=[[:space:]]*"medium"[[:space:]]*$' +assert_file_contains "$agents_path/terra-worker-max.toml" \ + '^name[[:space:]]*=[[:space:]]*"terra_worker_max"[[:space:]]*$' \ + '^description[[:space:]]*=[[:space:]]*"""' \ + '^developer_instructions[[:space:]]*=[[:space:]]*"""' \ + 'D2 work that stays within ordinary engineering judgment' \ + 'many' \ + 'coupled constraints' \ + 'Do not use for unresolved architectural trade-offs' \ + 'Use Max reasoning for coupled constraints, edge cases, and verification' \ + 'not to' \ + "broaden the task's capability boundary" \ + '^model[[:space:]]*=[[:space:]]*"gpt-5\.6-terra"[[:space:]]*$' \ + '^model_reasoning_effort[[:space:]]*=[[:space:]]*"max"[[:space:]]*$' + assert_file_contains "$agents_path/sol-specialist.toml" \ - '^name[[:space:]]*=[[:space:]]*"[^"]+"[[:space:]]*$' \ + '^name[[:space:]]*=[[:space:]]*"sol_specialist"[[:space:]]*$' \ '^description[[:space:]]*=[[:space:]]*"""' \ '^developer_instructions[[:space:]]*=[[:space:]]*"""' \ '^model[[:space:]]*=[[:space:]]*"gpt-5\.6-sol"[[:space:]]*$' \ '^model_reasoning_effort[[:space:]]*=[[:space:]]*"high"[[:space:]]*$' \ + '^sandbox_mode[[:space:]]*=[[:space:]]*"read-only"[[:space:]]*$' \ + 'Use as the default for one bounded D3' \ + 'Prefer sol_specialist_max when uncertainty and consequence are both' + +assert_file_contains "$agents_path/sol-specialist-max.toml" \ + '^name[[:space:]]*=[[:space:]]*"sol_specialist_max"[[:space:]]*$' \ + '^description[[:space:]]*=[[:space:]]*"""' \ + '^developer_instructions[[:space:]]*=[[:space:]]*"""' \ + 'D3 work when both uncertainty and consequence are high' \ + 'security-sensitive trade-offs' \ + 'reasoning variance' \ + '^model[[:space:]]*=[[:space:]]*"gpt-5\.6-sol"[[:space:]]*$' \ + '^model_reasoning_effort[[:space:]]*=[[:space:]]*"max"[[:space:]]*$' \ '^sandbox_mode[[:space:]]*=[[:space:]]*"read-only"[[:space:]]*$' +assert_file_absent "$agents_path/luna-task-high.toml" +assert_file_absent "$agents_path/terra-worker-high.toml" + if ((failures > 0)); then printf 'Task-Aware Agent validation failed with %d error(s).\n' "$failures" >&2 exit 1 @@ -113,7 +202,30 @@ fi if [[ "$skip_runtime" == false ]]; then if command -v codex >/dev/null 2>&1; then - CODEX_HOME="$codex_home" codex --strict-config doctor --summary --no-color --ascii + if [[ "$config_only_runtime" == true ]]; then + set +e + doctor_output=$(CODEX_HOME="$codex_home" codex --strict-config doctor --json --no-color 2>&1) + doctor_exit=$? + set -e + config_status=$(printf '%s\n' "$doctor_output" | awk ' + /"config.load"[[:space:]]*:/ { in_config = 1 } + in_config && /"status"[[:space:]]*:/ { + status = $0 + sub(/^.*"status"[[:space:]]*:[[:space:]]*"/, "", status) + sub(/".*$/, "", status) + print status + exit + } + ') + if [[ "$config_status" != ok ]]; then + printf '%s\n' "$doctor_output" >&2 + printf 'Codex strict config load failed with doctor exit %d for CODEX_HOME=%s.\n' "$doctor_exit" "$codex_home" >&2 + exit 1 + fi + printf 'Codex strict config load passed for CODEX_HOME=%s.\n' "$codex_home" + else + CODEX_HOME="$codex_home" codex --strict-config doctor --summary --no-color --ascii + fi else printf '%s\n' 'Warning: codex was not found; file validation passed but runtime validation was skipped.' >&2 fi