From 7699f67959b6a74a48982b9ef2e6e165aac8072d Mon Sep 17 00:00:00 2001 From: Bryan Ramos Date: Thu, 2 Apr 2026 07:05:43 -0400 Subject: [PATCH 01/36] fix: correct stale agent names, false cost claims, and document permissionMode override - Replace "Karen/Verification" with "reviewer/auditor" in worker-protocol - Reframe parallel dispatch rationale from false "~10% token cost" (only applies to fork agents with identical prefixes) to accurate wall-clock latency savings - Add Permission model section documenting that permissionMode is overridden when parent runs in acceptEdits mode; disallowedTools is the actual safety boundary --- skills/orchestrate/SKILL.md | 10 ++++++++-- skills/worker-protocol/SKILL.md | 4 ++-- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/skills/orchestrate/SKILL.md b/skills/orchestrate/SKILL.md index 5770b2a..6a95dc7 100644 --- a/skills/orchestrate/SKILL.md +++ b/skills/orchestrate/SKILL.md @@ -83,7 +83,7 @@ If the plan flags unresolved blockers or unverified assumptions, escalate to the For each wave in the plan: -1. **Spawn ALL workers in the wave in a single response.** This is not optional — it is a cost and performance requirement. Parallel workers share the same cached context prefix at ~10% token cost. Serializing independent workers wastes both money and time. +1. **Spawn ALL workers in the wave in a single response.** This is not optional — it is a performance requirement. Parallel agents run concurrently, reducing wall-clock time proportional to the number of agents. Serializing independent workers wastes time linearly. 2. Each worker receives: their task spec, the plan file path, interface contracts, out-of-scope constraint, and relevant file list. @@ -184,13 +184,19 @@ When multiple risk tags are present, take the union. Spawn all required reviewer **documenter** - Spawn after implementation wave is complete. Background. One instance per completed scope area. +### Permission model + +Agent `permissionMode` in frontmatter is overridden when the parent (you, the orchestrator) runs in `acceptEdits` or `bypassPermissions` mode — the child inherits the parent's mode. This means `permissionMode: plan` on read-only agents like architect, researcher, and reviewer is **not enforced at runtime**. + +The actual write protection for read-only agents comes from `disallowedTools: Write, Edit` — this is enforced regardless of permission mode. Do not rely on `permissionMode` as a safety boundary; rely on tool restrictions. + ### Parallelism mandate **Same-wave workers must be spawned in a single response.** **Reviewer and auditor must be spawned in a single response.** **All researchers must be spawned in a single response.** -Spawning agents sequentially when they could run in parallel is a protocol violation, not a style choice. Parallel agents share a cached context prefix — each additional parallel agent costs ~10% of what the first agent paid for that shared context. +Spawning agents sequentially when they could run in parallel is a protocol violation, not a style choice. Parallel dispatch reduces wall-clock latency proportionally — N agents in parallel complete in the time of the slowest, not the sum of all. ### Git flow diff --git a/skills/worker-protocol/SKILL.md b/skills/worker-protocol/SKILL.md index ddeb137..c188923 100644 --- a/skills/worker-protocol/SKILL.md +++ b/skills/worker-protocol/SKILL.md @@ -54,6 +54,6 @@ If blocked (tool failure, missing file, build error): try to work around it and ## Receiving reviewer feedback -Your orchestrator may resume you with findings from Karen (analytical review) or Verification (runtime/test review), or both. +Your orchestrator may resume you with findings from the reviewer (code quality + AC verification) or the auditor (security + runtime validation), or both. -You already have the task context and your previous work. Address the issues specified. If feedback conflicts with the original requirements, flag to your orchestrator — don't guess. Resubmit complete output in standard format. In Self-Assessment, note which issues you addressed and reference the reviewer (Karen / Verification) for each. +You already have the task context and your previous work. Address the issues specified. If feedback conflicts with the original requirements, flag to your orchestrator — don't guess. Resubmit complete output in standard format. In Self-Assessment, note which issues you addressed and reference the reviewer or auditor for each. From d99f89e5b230fdc41076675dc829a7df7e160027 Mon Sep 17 00:00:00 2001 From: Bryan Ramos Date: Thu, 2 Apr 2026 07:06:57 -0400 Subject: [PATCH 02/36] fix(reviewer): add permissionMode: plan for consistency with other read-only agents --- agents/reviewer.md | 1 + 1 file changed, 1 insertion(+) diff --git a/agents/reviewer.md b/agents/reviewer.md index fde73e2..a6b7a94 100644 --- a/agents/reviewer.md +++ b/agents/reviewer.md @@ -2,6 +2,7 @@ name: reviewer description: Use after implementation — reviews code quality and verifies claims against source, docs, and acceptance criteria. Never modifies code. model: sonnet +permissionMode: plan tools: Read, Glob, Grep, Bash, WebFetch, WebSearch disallowedTools: Write, Edit maxTurns: 20 From 71dc65376a44571d5dd23b282acf65d9a7bca82f Mon Sep 17 00:00:00 2001 From: Bryan Ramos Date: Thu, 2 Apr 2026 07:06:59 -0400 Subject: [PATCH 03/36] fix(settings): harden permission deny patterns for sensitive directories - Replace command-specific Bash denies (cat/less) with broad glob patterns that catch any command referencing .ssh, .aws, .gnupg, .env - Add Write/Edit deny rules for ~/.ssh, ~/.aws, ~/.gnupg to prevent writes, not just reads --- settings.json | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/settings.json b/settings.json index aa2692d..70c3161 100644 --- a/settings.json +++ b/settings.json @@ -22,13 +22,16 @@ "Read(~/.gnupg/**)", "Read(./.env)", "Read(./.env.*)", - "Bash(cat ~/.ssh/*)", - "Bash(cat ~/.aws/*)", - "Bash(cat ~/.gnupg/*)", - "Bash(cat .env*)", - "Bash(less ~/.ssh/*)", - "Bash(less ~/.aws/*)", - "Bash(less ~/.gnupg/*)" + "Write(~/.ssh/**)", + "Write(~/.aws/**)", + "Write(~/.gnupg/**)", + "Edit(~/.ssh/**)", + "Edit(~/.aws/**)", + "Edit(~/.gnupg/**)", + "Bash(*.ssh/*)", + "Bash(*.aws/*)", + "Bash(*.gnupg/*)", + "Bash(*.env*)" ], "ask": [ "Bash(rm *)", From 829ce2f78b7e7d2b05b18f1061dc8f8393f052a8 Mon Sep 17 00:00:00 2001 From: Bryan Ramos Date: Thu, 2 Apr 2026 07:07:00 -0400 Subject: [PATCH 04/36] feat(auditor): add WebFetch and WebSearch for CVE/vulnerability verification --- agents/auditor.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/agents/auditor.md b/agents/auditor.md index 0b1cdc7..8e715fe 100644 --- a/agents/auditor.md +++ b/agents/auditor.md @@ -3,7 +3,7 @@ name: auditor description: Use after implementation — audits for security vulnerabilities and validates runtime behavior. Builds, tests, and probes acceptance criteria. Never modifies code. model: sonnet background: true -tools: Read, Glob, Grep, Bash +tools: Read, Glob, Grep, Bash, WebFetch, WebSearch disallowedTools: Write, Edit maxTurns: 25 skills: From 3eccb72e209f05d9c80ba5dc35b183eefff59626 Mon Sep 17 00:00:00 2001 From: Bryan Ramos Date: Thu, 2 Apr 2026 07:07:01 -0400 Subject: [PATCH 05/36] fix(architect): change effort from max to high for external user compatibility --- agents/architect.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/agents/architect.md b/agents/architect.md index 45e2d2b..e078721 100644 --- a/agents/architect.md +++ b/agents/architect.md @@ -2,7 +2,7 @@ name: architect description: Research-first planning agent. Handles triage, research coordination, architecture design, and wave decomposition. Use before any non-trivial implementation task. Produces the implementation blueprint the entire team follows. model: opus -effort: max +effort: high permissionMode: plan tools: Read, Glob, Grep, WebFetch, WebSearch, Bash, Write disallowedTools: Edit From a788cd5d52044b9aae01f1061cbe9f65e62521f3 Mon Sep 17 00:00:00 2001 From: Bryan Ramos Date: Thu, 2 Apr 2026 07:07:01 -0400 Subject: [PATCH 06/36] docs: fix clone path in README to use generic location --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index b653a93..95e02b3 100644 --- a/README.md +++ b/README.md @@ -5,8 +5,8 @@ A portable Claude Code agent team configuration. Clone it, run `install.sh`, and ## Quick install ```bash -git clone ~/Documents/Personal/projects/agent-team -cd ~/Documents/Personal/projects/agent-team +git clone ~/agent-team +cd ~/agent-team ./install.sh ``` From ad26db334e2b69201c40ac2c531755fe20eb7438 Mon Sep 17 00:00:00 2001 From: Bryan Ramos Date: Thu, 2 Apr 2026 07:09:38 -0400 Subject: [PATCH 07/36] chore(settings): remove redundant default values MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Drop includeGitInstructions, syntaxHighlightingDisabled, and autoUpdatesChannel — all were set to their defaults. --- settings.json | 3 --- 1 file changed, 3 deletions(-) diff --git a/settings.json b/settings.json index 70c3161..ce0414e 100644 --- a/settings.json +++ b/settings.json @@ -4,7 +4,6 @@ "commit": "", "pr": "" }, - "includeGitInstructions": true, "permissions": { "allow": [ "Bash", @@ -51,9 +50,7 @@ "defaultMode": "acceptEdits" }, "model": "sonnet", - "syntaxHighlightingDisabled": false, "effortLevel": "medium", - "autoUpdatesChannel": "stable", "claudeMdExcludes": [ ".claude/agent-memory/**" ] From cba5f5d99ec705c34b829442c2a6ed73c1e8b744 Mon Sep 17 00:00:00 2001 From: Bryan Ramos Date: Thu, 2 Apr 2026 07:09:39 -0400 Subject: [PATCH 08/36] feat(worker): default to worktree isolation for safe parallel execution --- agents/worker.md | 1 + 1 file changed, 1 insertion(+) diff --git a/agents/worker.md b/agents/worker.md index 7be517b..153323a 100644 --- a/agents/worker.md +++ b/agents/worker.md @@ -3,6 +3,7 @@ name: worker description: Universal implementer. Handles all task tiers — trivial to architectural. Model is scaled by the orchestrator based on task complexity (haiku for trivial, sonnet for standard, opus for architectural/ambiguous). Default implementer for all implementation work. model: sonnet permissionMode: acceptEdits +isolation: worktree tools: Read, Write, Edit, Glob, Grep, Bash maxTurns: 25 skills: From 53e3814ba845baa57173084035ec61e57a5162c5 Mon Sep 17 00:00:00 2001 From: Bryan Ramos Date: Thu, 2 Apr 2026 07:09:40 -0400 Subject: [PATCH 09/36] feat(skills): add when_to_use frontmatter for skill discovery guidance --- skills/conventions/SKILL.md | 1 + skills/orchestrate/SKILL.md | 1 + 2 files changed, 2 insertions(+) diff --git a/skills/conventions/SKILL.md b/skills/conventions/SKILL.md index c7e126d..522f3dc 100644 --- a/skills/conventions/SKILL.md +++ b/skills/conventions/SKILL.md @@ -1,6 +1,7 @@ --- name: conventions description: Core coding conventions and quality priorities for all projects. +when_to_use: Automatically loaded by agents via skills frontmatter. Load manually when you need to reference project coding standards, commit format, or quality priorities. --- ## Quality priorities (in order) diff --git a/skills/orchestrate/SKILL.md b/skills/orchestrate/SKILL.md index 6a95dc7..b4b27ef 100644 --- a/skills/orchestrate/SKILL.md +++ b/skills/orchestrate/SKILL.md @@ -1,6 +1,7 @@ --- name: orchestrate description: Orchestration framework for decomposing and delegating complex tasks to the agent team. Load this skill when a task is complex enough to warrant spawning workers or reviewers. Covers task tiers, planning pipeline, wave dispatch, review, and git flow. +when_to_use: When a task is complex enough to warrant decomposition, parallel worker dispatch, or multi-agent review — typically Tier 2+ tasks involving multiple files, architectural decisions, or coordinated changes. --- You are now acting as orchestrator. Decompose, delegate, validate, deliver. Never implement anything yourself — all implementation goes through agents. From f0cb4e840a1446bf693ec5f32185345cff2f2eab Mon Sep 17 00:00:00 2001 From: Bryan Ramos Date: Thu, 2 Apr 2026 07:09:41 -0400 Subject: [PATCH 10/36] chore(memory): clean MEMORY.md to pure index format --- .claude/memory/MEMORY.md | 4 ---- 1 file changed, 4 deletions(-) diff --git a/.claude/memory/MEMORY.md b/.claude/memory/MEMORY.md index 8c30b36..6ea29cf 100644 --- a/.claude/memory/MEMORY.md +++ b/.claude/memory/MEMORY.md @@ -1,5 +1 @@ -# Project Memory - -Index of persistent memory for the agent-team project. - - [TODO: inter-agent JSON schema](todo_inter_agent_schema.md) — formal typed schema for all inter-agent messages to replace freetext signals From d2fdcbc73168fc88c5a951463c82f05467c19dc9 Mon Sep 17 00:00:00 2001 From: Bryan Ramos Date: Thu, 2 Apr 2026 07:17:36 -0400 Subject: [PATCH 11/36] refactor: modularize CLAUDE.md into ~/.claude/rules/ with agent-team content separation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Split monolithic CLAUDE.md into 7 focused rule files in rules/ - Remove agent-team specific content from global rules (subagent cost, model defaults, tier shortcuts) — already covered by orchestrate skill - Add "prefer summaries over verbatim output" to orchestrate Step 8 - Update install.sh to symlink rules/ directory - Trim CLAUDE.md to a minimal pointer since rules auto-load --- CLAUDE.md | 70 ++----------------------------------- README.md | 6 +++- install.sh | 3 ++ rules/01-session.md | 12 +++++++ rules/02-responses.md | 6 ++++ rules/03-git.md | 6 ++++ rules/04-tools.md | 17 +++++++++ rules/05-verification.md | 10 ++++++ rules/06-nix.md | 9 +++++ rules/07-research.md | 5 +++ skills/orchestrate/SKILL.md | 2 +- 11 files changed, 76 insertions(+), 70 deletions(-) create mode 100644 rules/01-session.md create mode 100644 rules/02-responses.md create mode 100644 rules/03-git.md create mode 100644 rules/04-tools.md create mode 100644 rules/05-verification.md create mode 100644 rules/06-nix.md create mode 100644 rules/07-research.md diff --git a/CLAUDE.md b/CLAUDE.md index b7977f3..ea55f37 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,70 +1,4 @@ # Global Claude Code Instructions -## Session Behavior -- Treat each session as stateless — do not assume context from prior sessions -- The CLAUDE.md hierarchy is the only source of persistent context -- If something needs to carry forward across sessions, it belongs in a CLAUDE.md file, not in session memory - -## Project Memory -- Project-specific memory lives in `.claude/memory/` at the project root -- Use `MEMORY.md` in that directory as the index (one line per entry pointing to a file) -- Memory files use frontmatter: `name`, `description`, `type` (user/feedback/project/reference) -- Commit `.claude/memory/` with the repo so memory persists across machines and sessions - - -## Commits & Git Workflow -- Make many small, tightly scoped commits — one logical change per commit -- Follow conventional commit format per the conventions skill -- Ask before pushing to remote or force-pushing -- Ask before opening PRs unless explicitly told to - -## Responses & Explanations -- Be concise — lead with the action or answer, not the preamble -- Include just enough reasoning to explain *why* a decision was made, not a full walkthrough -- Skip trailing summaries ("Here's what I did...") — the diff speaks for itself -- No emojis unless explicitly asked - -## Tool & Approach Philosophy -- Prefer tools and solutions that are declarative and reproducible over imperative one-offs -- Portability across dev environments is a first-class concern — avoid hardcoding machine-specific paths or assumptions -- The right tool for the job is the right tool — no language/framework bias, but favor things that can be version-pinned and reproduced - -## Parallelism -- Always parallelize independent work — tool calls, subagents, file reads, searches -- When a task has components that don't depend on each other, run them concurrently by default -- Spin up subagents for distinct workstreams (audits, refactors, tests, docs) rather than working sequentially -- Subagents default to Sonnet for cost efficiency; agent frontmatter overrides where capability requires a different model -- Sequential execution should be the exception, not the default - -## Cost Awareness -- Subagent outputs should be concise — return the deliverable, not the reasoning -- When subagent results return to main context, prefer summaries over verbatim output -- Not every task needs the full planning pipeline — Tier 1 tasks with obvious approaches can go straight to worker dispatch - -## Verification -- After making changes, run relevant tests or build commands to verify correctness before reporting success -- If no tests exist for the changed code, say so rather than silently assuming it works -- Prefer running single targeted tests over the full suite unless asked otherwise - -## Context Management -- Use subagents for exploratory reads and investigations to keep the main context clean -- Prefer scoped file reads (offset/limit) over reading entire large files -- When a task is complete or the topic shifts significantly, suggest /clear - -## When Things Go Wrong -- If an approach fails twice, stop and reassess rather than continuing to iterate -- Present the failure clearly and propose an alternative before proceeding - -## Nix -- Nix is the preferred meta package manager on all systems — assume it is available even on non-NixOS Linux -- Always prefer a project-level `flake.nix` as the canonical way to define dev environments, build systems, and scripts -- Dev environments go in `devShells`, project scripts/tools go in `packages` or as `apps` within the flake -- Never suggest `apt`, `brew`, `pip install --user`, `npm install -g`, or other imperative global installs — reach for `nix shell`, `nix run`, or the project devshell instead -- Prefer `nix run` for one-off tool invocations and `nix develop` (or `direnv` + `use flake`) for persistent dev shells -- Binaries and tools introduced to a project should be pinned and run through Nix, not assumed to be on `$PATH` from the host -- Flakes are the preferred interface — avoid legacy `nix-env` or channel-based patterns - -## Research Before Acting -- Before implementing a solution, research it — read relevant documentation, search for existing patterns, check official sources -- Do not reason from first principles when documentation or prior art exists -- Prefer verified answers over confident guesses +Rules are modularized in `~/.claude/rules/` and loaded automatically. +Agent-team specific protocols live in skills (orchestrate, conventions, worker-protocol, qa-checklist). diff --git a/README.md b/README.md index 95e02b3..21bb649 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ cd ~/agent-team ./install.sh ``` -The script symlinks `agents/`, `skills/`, `CLAUDE.md`, and `settings.json` into `~/.claude/`. Works on Linux, macOS, and Windows (Git Bash). +The script symlinks `agents/`, `skills/`, `rules/`, `CLAUDE.md`, and `settings.json` into `~/.claude/`. Works on Linux, macOS, and Windows (Git Bash). ## Maintenance @@ -38,6 +38,10 @@ The script symlinks `agents/`, `skills/`, `CLAUDE.md`, and `settings.json` into | `qa-checklist` | Self-validation checklist workers run before returning results | | `project` | Instructs agents to check for and ingest a project-specific skill file before starting work | +## Rules + +Global instructions are modularized in `rules/` and auto-loaded by Claude Code from `~/.claude/rules/` on every session. Each file covers a focused topic (git workflow, Nix preferences, response style, etc.). Agent-team specific protocols live in skills, not rules. + ## How to use In an interactive Claude Code session, load the orchestrate skill when a task is complex enough to warrant delegation: diff --git a/install.sh b/install.sh index b68a323..50fbf87 100755 --- a/install.sh +++ b/install.sh @@ -8,8 +8,10 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" CLAUDE_DIR="$HOME/.claude" AGENTS_SRC="$SCRIPT_DIR/agents" SKILLS_SRC="$SCRIPT_DIR/skills" +RULES_SRC="$SCRIPT_DIR/rules" AGENTS_DST="$CLAUDE_DIR/agents" SKILLS_DST="$CLAUDE_DIR/skills" +RULES_DST="$CLAUDE_DIR/rules" CLAUDE_MD_SRC="$SCRIPT_DIR/CLAUDE.md" CLAUDE_MD_DST="$CLAUDE_DIR/CLAUDE.md" SETTINGS_SRC="$SCRIPT_DIR/settings.json" @@ -118,6 +120,7 @@ create_file_symlink() { create_symlink "$AGENTS_SRC" "$AGENTS_DST" "agents" create_symlink "$SKILLS_SRC" "$SKILLS_DST" "skills" +create_symlink "$RULES_SRC" "$RULES_DST" "rules" create_file_symlink "$CLAUDE_MD_SRC" "$CLAUDE_MD_DST" "CLAUDE.md" create_file_symlink "$SETTINGS_SRC" "$SETTINGS_DST" "settings.json" diff --git a/rules/01-session.md b/rules/01-session.md new file mode 100644 index 0000000..f3db2d6 --- /dev/null +++ b/rules/01-session.md @@ -0,0 +1,12 @@ +# Session Behavior + +- Treat each session as stateless — do not assume context from prior sessions +- The CLAUDE.md hierarchy is the only source of persistent context +- If something needs to carry forward across sessions, it belongs in a CLAUDE.md file, not in session memory + +# Project Memory + +- Project-specific memory lives in `.claude/memory/` at the project root +- Use `MEMORY.md` in that directory as the index (one line per entry pointing to a file) +- Memory files use frontmatter: `name`, `description`, `type` (user/feedback/project/reference) +- Commit `.claude/memory/` with the repo so memory persists across machines and sessions diff --git a/rules/02-responses.md b/rules/02-responses.md new file mode 100644 index 0000000..2dbbdf1 --- /dev/null +++ b/rules/02-responses.md @@ -0,0 +1,6 @@ +# Responses & Explanations + +- Be concise — lead with the action or answer, not the preamble +- Include just enough reasoning to explain *why* a decision was made, not a full walkthrough +- Skip trailing summaries ("Here's what I did...") — the diff speaks for itself +- No emojis unless explicitly asked diff --git a/rules/03-git.md b/rules/03-git.md new file mode 100644 index 0000000..d131578 --- /dev/null +++ b/rules/03-git.md @@ -0,0 +1,6 @@ +# Commits & Git Workflow + +- Make many small, tightly scoped commits — one logical change per commit +- Follow conventional commit format per the conventions skill +- Ask before pushing to remote or force-pushing +- Ask before opening PRs unless explicitly told to diff --git a/rules/04-tools.md b/rules/04-tools.md new file mode 100644 index 0000000..4bb7eef --- /dev/null +++ b/rules/04-tools.md @@ -0,0 +1,17 @@ +# Tool & Approach Philosophy + +- Prefer tools and solutions that are declarative and reproducible over imperative one-offs +- Portability across dev environments is a first-class concern — avoid hardcoding machine-specific paths or assumptions +- The right tool for the job is the right tool — no language/framework bias, but favor things that can be version-pinned and reproduced + +# Parallelism + +- Always parallelize independent work — tool calls, file reads, searches +- When a task has components that don't depend on each other, run them concurrently by default +- Sequential execution should be the exception, not the default + +# Context Management + +- Use subagents for exploratory reads and investigations to keep the main context clean +- Prefer scoped file reads (offset/limit) over reading entire large files +- When a task is complete or the topic shifts significantly, suggest /clear diff --git a/rules/05-verification.md b/rules/05-verification.md new file mode 100644 index 0000000..2d51096 --- /dev/null +++ b/rules/05-verification.md @@ -0,0 +1,10 @@ +# Verification + +- After making changes, run relevant tests or build commands to verify correctness before reporting success +- If no tests exist for the changed code, say so rather than silently assuming it works +- Prefer running single targeted tests over the full suite unless asked otherwise + +# When Things Go Wrong + +- If an approach fails twice, stop and reassess rather than continuing to iterate +- Present the failure clearly and propose an alternative before proceeding diff --git a/rules/06-nix.md b/rules/06-nix.md new file mode 100644 index 0000000..cd7a8f1 --- /dev/null +++ b/rules/06-nix.md @@ -0,0 +1,9 @@ +# Nix + +- Nix is the preferred meta package manager on all systems — assume it is available even on non-NixOS Linux +- Always prefer a project-level `flake.nix` as the canonical way to define dev environments, build systems, and scripts +- Dev environments go in `devShells`, project scripts/tools go in `packages` or as `apps` within the flake +- Never suggest `apt`, `brew`, `pip install --user`, `npm install -g`, or other imperative global installs — reach for `nix shell`, `nix run`, or the project devshell instead +- Prefer `nix run` for one-off tool invocations and `nix develop` (or `direnv` + `use flake`) for persistent dev shells +- Binaries and tools introduced to a project should be pinned and run through Nix, not assumed to be on `$PATH` from the host +- Flakes are the preferred interface — avoid legacy `nix-env` or channel-based patterns diff --git a/rules/07-research.md b/rules/07-research.md new file mode 100644 index 0000000..21f99ea --- /dev/null +++ b/rules/07-research.md @@ -0,0 +1,5 @@ +# Research Before Acting + +- Before implementing a solution, research it — read relevant documentation, search for existing patterns, check official sources +- Do not reason from first principles when documentation or prior art exists +- Prefer verified answers over confident guesses diff --git a/skills/orchestrate/SKILL.md b/skills/orchestrate/SKILL.md index b4b27ef..deaf870 100644 --- a/skills/orchestrate/SKILL.md +++ b/skills/orchestrate/SKILL.md @@ -132,7 +132,7 @@ Collect both verdicts before deciding whether to advance to the next wave or sen - **Docs:** if documentation was in scope, spawn `documenter` now with final implementation as context - **Package:** list what was done by logical area (not by worker). Include all file paths. Surface PASS WITH NOTES caveats as a brief "Heads up" section. -Lead with the result. Don't expose worker IDs, wave counts, or internal mechanics. +Lead with the result. Don't expose worker IDs, wave counts, or internal mechanics. When subagent results return to your context, prefer concise summaries over verbatim output — the full detail is in the code, not the report. --- From 341f500396537de1b3738e93f71d12f41a06bde7 Mon Sep 17 00:00:00 2001 From: Bryan Ramos Date: Thu, 2 Apr 2026 07:38:02 -0400 Subject: [PATCH 12/36] feat: add typed inter-agent communication schema Replace freetext signals (RFR, LGTM, VERDICT: PASS) with YAML frontmatter envelopes routed by a `signal` field. New message-schema skill defines 12 message types covering worker submissions, review/audit verdicts, triage/plan results, research results, and orchestrator commands. All agents load the skill; qa-checklist enforces compliance; orchestrate routes by envelope signal. --- .claude/memory/MEMORY.md | 2 +- .claude/memory/todo_inter_agent_schema.md | 12 +- README.md | 1 + agents/architect.md | 41 ++- agents/auditor.md | 26 +- agents/debugger.md | 1 + agents/documenter.md | 1 + agents/researcher.md | 16 ++ agents/reviewer.md | 24 +- agents/worker.md | 1 + skills/message-schema/SKILL.md | 309 ++++++++++++++++++++++ skills/orchestrate/SKILL.md | 40 ++- skills/qa-checklist/SKILL.md | 12 +- skills/worker-protocol/SKILL.md | 29 +- 14 files changed, 476 insertions(+), 39 deletions(-) create mode 100644 skills/message-schema/SKILL.md diff --git a/.claude/memory/MEMORY.md b/.claude/memory/MEMORY.md index 6ea29cf..897a83b 100644 --- a/.claude/memory/MEMORY.md +++ b/.claude/memory/MEMORY.md @@ -1 +1 @@ -- [TODO: inter-agent JSON schema](todo_inter_agent_schema.md) — formal typed schema for all inter-agent messages to replace freetext signals +- [Inter-agent communication schema](todo_inter_agent_schema.md) — YAML frontmatter envelopes implemented via message-schema skill diff --git a/.claude/memory/todo_inter_agent_schema.md b/.claude/memory/todo_inter_agent_schema.md index 3ddd8f6..4e40704 100644 --- a/.claude/memory/todo_inter_agent_schema.md +++ b/.claude/memory/todo_inter_agent_schema.md @@ -1,11 +1,13 @@ --- -name: TODO — formal JSON schema for inter-agent communication -description: Planned work to replace informal signal/text conventions with a typed JSON schema for all inter-agent messages +name: Inter-agent communication schema — implemented +description: Typed YAML frontmatter envelopes for all inter-agent messages, replacing freetext signals. Defined in skills/message-schema/SKILL.md. type: project --- -Define a formal JSON schema for all inter-agent communication in the agent team. +Formal inter-agent communication schema implemented via the `message-schema` skill. -**Why:** Current protocol relies on freetext signals (RFR, LGTM, REVISE, VERDICT: PASS, etc.) and unstructured prose output. A typed schema would make messages machine-readable, easier to validate, and more reliable for orchestrator parsing — especially as parallelism increases and the orchestrator is managing multiple concurrent agent outputs. +**What:** All agent output and orchestrator dispatch uses YAML frontmatter envelopes with a `signal` field as the primary routing key. 12 message types cover worker submissions, review/audit verdicts, triage/plan results, research results, and orchestrator commands. -**How to apply:** Design the schema before any further changes to the orchestrate skill or agent protocols. All agent output formats (reviewer verdict, auditor verdict, worker RFR, architect triage response, etc.) should conform to it. Consider whether the schema lives as a skill, a standalone JSON Schema file, or embedded in agent frontmatter. +**Why:** Freetext signals (RFR, LGTM, VERDICT: PASS) were ambiguous and required prose parsing. Typed envelopes give the orchestrator a consistent, unambiguous routing key. + +**How to apply:** Every agent loads the `message-schema` skill. The qa-checklist includes schema compliance checks. The orchestrate skill routes by reading the `signal` field from agent output envelopes. diff --git a/README.md b/README.md index 21bb649..3424b74 100644 --- a/README.md +++ b/README.md @@ -36,6 +36,7 @@ The script symlinks `agents/`, `skills/`, `rules/`, `CLAUDE.md`, and `settings.j | `conventions` | Core coding conventions and quality priorities shared by all agents | | `worker-protocol` | Output format, feedback handling, and operational procedures for worker agents | | `qa-checklist` | Self-validation checklist workers run before returning results | +| `message-schema` | Typed YAML frontmatter envelopes for all inter-agent communication | | `project` | Instructs agents to check for and ingest a project-specific skill file before starting work | ## Rules diff --git a/agents/architect.md b/agents/architect.md index e078721..996ae0c 100644 --- a/agents/architect.md +++ b/agents/architect.md @@ -9,6 +9,7 @@ disallowedTools: Edit maxTurns: 35 skills: - conventions + - message-schema - project --- @@ -47,7 +48,20 @@ Triggered when the orchestrator sends you a raw request without a `## Research C 4. Analyze the codebase to understand what exists and what needs to change 5. Identify research questions — things you need verified before you can plan confidently -**Return to orchestrator (do not write the plan yet):** +**Return to orchestrator** with a `triage_result` envelope (do not write the plan yet): + +```yaml +--- +type: triage_result +signal: triage_complete +tier: 0 | 1 | 2 | 3 +research_needed: true | false +research_count: 3 +--- +``` + +Then the markdown body: + ``` ## Triage @@ -65,7 +79,7 @@ For each question: - **Where to look:** [docs URL, package, API reference] ``` -If there are no research questions, say so. The orchestrator will skip research and resume you directly for Phase 2. +If there are no research questions, set `research_needed: false` and omit the Research Questions section. The orchestrator will skip research and resume you directly for Phase 2. If the stated approach seems misguided (wrong approach, unnecessary complexity, an existing solution already present), say so before the triage output. Propose the better path. @@ -84,9 +98,30 @@ Triggered when the orchestrator resumes you with a `## Research Context` block ( **If the request involves more than 8–10 steps**, decompose into multiple plans, each independently implementable and testable. State: "This is plan 1 of N." +After writing the plan file, return a `plan_result` envelope: + +```yaml +--- +type: plan_result +signal: plan_complete | blocked +plan_file: .claude/plans/kebab-case-title.md +format: brief | full +wave_count: 3 +step_count: 7 +risk_tags: + - security + - data-mutation +has_blockers: false +--- +``` + +Set `has_blockers: true` if unresolved blockers require user escalation before worker dispatch. + +Body: One-paragraph summary of what the plan covers. + --- -## Output formats +## Plan formats ### Format selection diff --git a/agents/auditor.md b/agents/auditor.md index 8e715fe..1aabe70 100644 --- a/agents/auditor.md +++ b/agents/auditor.md @@ -8,6 +8,7 @@ disallowedTools: Write, Edit maxTurns: 25 skills: - conventions + - message-schema - project --- @@ -61,6 +62,27 @@ For every security finding: explain the attack vector, reference the relevant CW ## Output format +Wrap your output in an `audit_verdict` envelope per the message-schema skill: + +```yaml +--- +type: audit_verdict +signal: pass | pass_with_notes | fail +security_findings: + critical: 0 + high: 0 + medium: 0 + low: 0 +build_status: pass | fail | skipped +test_status: pass | fail | partial | skipped +typecheck_status: pass | fail | skipped +--- +``` + +**Hard rule:** `security_findings.critical > 0` or `build_status: fail` or `test_status: fail` requires `signal: fail`. + +Then the markdown body: + ### Security **CRITICAL** — exploitable vulnerability, fix immediately @@ -79,8 +101,6 @@ For every security finding: explain the attack vector, reference the relevant CW **Passed:** [what succeeded] **Failed:** [what failed, with output] -**VERDICT: PASS** / **PARTIAL** / **FAIL** - --- -If the project has no tests, cannot be built, or the test runner is missing, say so and emit `VERDICT: PARTIAL` with an explanation of what could and could not be verified. Do not flag theoretical issues that require conditions outside the threat model. +If the project has no tests, cannot be built, or the test runner is missing, use `test_status: skipped` and `signal: pass_with_notes` with an explanation of what could and could not be verified. Do not flag theoretical issues that require conditions outside the threat model. diff --git a/agents/debugger.md b/agents/debugger.md index d58b180..329fd5c 100644 --- a/agents/debugger.md +++ b/agents/debugger.md @@ -8,6 +8,7 @@ maxTurns: 20 skills: - conventions - worker-protocol + - message-schema - project --- diff --git a/agents/documenter.md b/agents/documenter.md index 14b6780..da5a203 100644 --- a/agents/documenter.md +++ b/agents/documenter.md @@ -10,6 +10,7 @@ maxTurns: 20 skills: - conventions - worker-protocol + - message-schema - project --- diff --git a/agents/researcher.md b/agents/researcher.md index 9bfa62a..df773d0 100644 --- a/agents/researcher.md +++ b/agents/researcher.md @@ -6,6 +6,8 @@ permissionMode: plan tools: Read, Glob, Grep, Bash, WebFetch, WebSearch disallowedTools: Write, Edit maxTurns: 10 +skills: + - message-schema --- You are a researcher. You answer one specific research question with verified facts. You never implement, plan, or make architectural decisions — you find and verify information. @@ -29,6 +31,20 @@ You are a researcher. You answer one specific research question with verified fa ## Output format +Wrap your output in a `research_result` envelope per the message-schema skill: + +```yaml +--- +type: research_result +signal: research_complete +topic: "brief topic identifier" +verified: true | false +has_gotchas: true | false +--- +``` + +Then the markdown body: + ``` ## Research: [topic] diff --git a/agents/reviewer.md b/agents/reviewer.md index a6b7a94..74edf29 100644 --- a/agents/reviewer.md +++ b/agents/reviewer.md @@ -8,6 +8,7 @@ disallowedTools: Write, Edit maxTurns: 20 skills: - conventions + - message-schema - project --- @@ -39,6 +40,25 @@ On **resubmissions**, the orchestrator will include a delta of what changed. Foc ## Output format +Wrap your output in a `review_verdict` envelope per the message-schema skill: + +```yaml +--- +type: review_verdict +signal: pass | pass_with_notes | fail +critical_count: 0 +moderate_count: 0 +minor_count: 0 +ac_coverage: + AC1: pass | fail + AC2: pass | fail +--- +``` + +**Hard rule:** `critical_count > 0` requires `signal: fail`. + +Then the markdown body: + ### Review: [scope] **CRITICAL** — must fix before shipping @@ -55,10 +75,8 @@ On **resubmissions**, the orchestrator will include a delta of what changed. Foc - AC2: PASS / FAIL — [one line] - ... -**VERDICT: PASS** / **PASS WITH NOTES** / **FAIL** - One line summary. --- -Keep it tight. One line per issue unless the explanation genuinely needs more. Reference file:line for every finding. If nothing is wrong, return `VERDICT: PASS` + 1-line summary. +Keep it tight. One line per issue unless the explanation genuinely needs more. Reference file:line for every finding. If nothing is wrong, return `signal: pass` + 1-line summary. diff --git a/agents/worker.md b/agents/worker.md index 153323a..1401d68 100644 --- a/agents/worker.md +++ b/agents/worker.md @@ -9,6 +9,7 @@ maxTurns: 25 skills: - conventions - worker-protocol + - message-schema - qa-checklist - project --- diff --git a/skills/message-schema/SKILL.md b/skills/message-schema/SKILL.md new file mode 100644 index 0000000..63f993f --- /dev/null +++ b/skills/message-schema/SKILL.md @@ -0,0 +1,309 @@ +--- +name: message-schema +description: Typed envelope schema for all inter-agent communication. Defines message types, required fields, and signal routing contracts. +when_to_use: Automatically loaded by all agents and the orchestrator via skills frontmatter. Reference when producing or consuming agent output. +--- + +Every agent output and orchestrator dispatch uses a **YAML frontmatter envelope** followed by a **markdown body**. The envelope contains routing metadata; the body contains human-readable content. + +``` +--- +type: +signal: +# ... type-specific fields +--- + +[markdown body] +``` + +The `signal` field is the orchestrator's primary routing key. It determines the next action without parsing prose. + +--- + +## Signals + +### Agent → Orchestrator + +| Signal | Meaning | Emitted by | +|--------|---------|------------| +| `rfr` | Work complete, ready for review | worker, debugger, documenter | +| `pass` | Review/audit passed cleanly | reviewer, auditor | +| `pass_with_notes` | Passed with non-blocking findings | reviewer, auditor | +| `fail` | Review/audit failed, needs rework | reviewer, auditor | +| `triage_complete` | Triage done, research questions identified (or none) | architect | +| `plan_complete` | Plan written to file | architect | +| `research_complete` | Research question answered | researcher | +| `blocked` | Cannot proceed, needs orchestrator intervention | any agent | +| `escalate` | Beyond agent scope, needs user decision | any agent | + +### Orchestrator → Agent + +| Signal | Meaning | Sent to | +|--------|---------|---------| +| `execute` | Perform this task | worker, debugger, documenter, architect | +| `revise` | Fix listed issues and resubmit | worker, debugger, documenter | +| `lgtm` | Approved, commit now | worker, debugger, documenter | +| `research` | Answer this research question | researcher | +| `plan` | Produce architecture and wave decomposition | architect | + +--- + +## Agent → Orchestrator Message Types + +### worker_submission + +Emitted by: worker, debugger, documenter + +```yaml +--- +type: worker_submission +signal: rfr | blocked | escalate +files_changed: + - path/to/file1 + - path/to/file2 +ac_coverage: + AC1: pass | fail | partial | na + AC2: pass | fail | partial | na +qa_check: pass | fail +--- +``` + +Required: `type`, `signal`, `files_changed`, `qa_check` +Optional: `ac_coverage` (omit when no AC provided in assignment) + +Body: `## Result` section with implementation details, then `## Self-Assessment` with per-criterion notes and known limitations. + +### review_verdict + +Emitted by: reviewer + +```yaml +--- +type: review_verdict +signal: pass | pass_with_notes | fail +critical_count: 0 +moderate_count: 2 +minor_count: 1 +ac_coverage: + AC1: pass | fail + AC2: pass | fail +--- +``` + +Required: `type`, `signal`, `critical_count`, `ac_coverage` +Optional: `moderate_count`, `minor_count` + +**Hard rule:** `critical_count > 0` requires `signal: fail`. + +Body: Findings by severity (CRITICAL / MODERATE / MINOR), then AC Coverage details, then one-line summary. + +### audit_verdict + +Emitted by: auditor + +```yaml +--- +type: audit_verdict +signal: pass | pass_with_notes | fail +security_findings: + critical: 0 + high: 0 + medium: 0 + low: 0 +build_status: pass | fail | skipped +test_status: pass | fail | partial | skipped +typecheck_status: pass | fail | skipped +--- +``` + +Required: `type`, `signal`, `security_findings`, `build_status`, `test_status` +Optional: `typecheck_status` + +**Hard rule:** `security_findings.critical > 0` or `build_status: fail` or `test_status: fail` requires `signal: fail`. + +Body: Security findings by severity (or CLEAN), then Runtime section with tested/passed/failed. + +### triage_result + +Emitted by: architect (Phase 1) + +```yaml +--- +type: triage_result +signal: triage_complete +tier: 0 | 1 | 2 | 3 +research_needed: true | false +research_count: 3 +--- +``` + +Required: `type`, `signal`, `tier`, `research_needed` +Optional: `research_count` (present when `research_needed: true`) + +**Routing:** `research_needed: false` means the orchestrator skips research and resumes architect directly for Phase 2. + +Body: Triage section (Tier, Problem, Constraints, Success criteria, Out of scope), then Research Questions if any. + +### plan_result + +Emitted by: architect (Phase 2) + +```yaml +--- +type: plan_result +signal: plan_complete | blocked +plan_file: .claude/plans/kebab-case-title.md +format: brief | full +wave_count: 3 +step_count: 7 +risk_tags: + - security + - data-mutation +has_blockers: false +--- +``` + +Required: `type`, `signal`, `plan_file`, `wave_count`, `risk_tags` +Optional: `format`, `step_count`, `has_blockers` + +**Routing:** `has_blockers: true` triggers user escalation before worker dispatch. + +Body: One-paragraph summary of what the plan covers. + +### research_result + +Emitted by: researcher + +```yaml +--- +type: research_result +signal: research_complete +topic: "brief topic identifier" +verified: true | false +has_gotchas: true | false +--- +``` + +Required: `type`, `signal`, `topic`, `verified` +Optional: `has_gotchas` + +**Routing:** `verified: false` flags unverified assumptions to the architect before planning. + +Body: Answer, Verified Facts with sources, Version Constraints, Gotchas, Unverified claims. + +--- + +## Orchestrator → Agent Message Types + +### task_assignment + +Sent to: worker, debugger, documenter + +```yaml +--- +type: task_assignment +signal: execute +task: "short task title" +plan_file: .claude/plans/kebab-case-title.md +wave: 1 +step: 2 +--- +``` + +Required: `type`, `signal` +Optional: `task`, `plan_file`, `wave`, `step` (Tier 0 tasks may lack plan context) + +Body: Task spec, Acceptance Criteria, Context (interface contracts, constraints, out-of-scope), Files to modify/read. + +### revision_request + +Sent to: worker, debugger, documenter + +```yaml +--- +type: revision_request +signal: revise +iteration: 2 +max_iterations: 5 +fix_severity: critical | critical+moderate | all +--- +``` + +Required: `type`, `signal`, `iteration` +Optional: `max_iterations`, `fix_severity` + +`fix_severity` maps to iteration: 1-3 = `all`, 4-5 = `critical`. + +Body: Issues to fix (from reviewer and/or auditor), grouped by source, with guidance. + +### approval + +Sent to: worker, debugger, documenter + +```yaml +--- +type: approval +signal: lgtm +--- +``` + +Required: `type`, `signal`. Pure control signal — commit using conventional commit format. + +### triage_request + +Sent to: architect (Phase 1) + +```yaml +--- +type: triage_request +signal: execute +--- +``` + +Required: `type`, `signal` + +Body: Raw user request and any relevant project context. + +### architecture_request + +Sent to: architect (Phase 2, resume) + +```yaml +--- +type: architecture_request +signal: plan +--- +``` + +Required: `type`, `signal` + +Body: Assembled `## Research Context` block from all researchers, or "No research needed — proceed." + +### research_request + +Sent to: researcher + +```yaml +--- +type: research_request +signal: research +topic: "brief topic identifier" +--- +``` + +Required: `type`, `signal`, `topic` + +Body: Specific question, why it matters (what decision it gates), where to look, relevant project context. + +--- + +## Schema Compliance + +Before returning output, verify: + +1. Output starts with a valid YAML frontmatter envelope (`---` delimiters) +2. `type` matches your message type +3. `signal` uses a valid enum value for your direction (agent→orch or orch→agent) +4. All required fields for your message type are present +5. Enum fields use exact values from this schema (no variations like "PASS" vs "pass") +6. Hard rules are satisfied (e.g., `critical_count > 0` implies `signal: fail`) diff --git a/skills/orchestrate/SKILL.md b/skills/orchestrate/SKILL.md index deaf870..a940ded 100644 --- a/skills/orchestrate/SKILL.md +++ b/skills/orchestrate/SKILL.md @@ -108,12 +108,17 @@ After each wave, spawn `reviewer` and `auditor` in a single response. They run i Both receive: worker output, plan file path, acceptance criteria list, risk tags. -Collect both verdicts before deciding whether to advance to the next wave or send back for fixes. +**Routing by envelope:** Read the `signal` field from each reviewer/auditor envelope: +- `signal: pass` → advance to next wave +- `signal: pass_with_notes` → advance, surface notes in final delivery +- `signal: fail` → check `critical_count` / `security_findings` and send worker to fix + +Do not advance until both verdicts are collected. ### Step 7 — Feedback loop on issues -1. Resume the worker with reviewer findings and instruction to fix -2. On resubmission, spawn reviewer again (new instance — stateless) +1. Resume the worker with a `revision_request` envelope containing reviewer/auditor findings +2. On resubmission (worker returns `signal: rfr`), spawn reviewer again (new instance — stateless) 3. Repeat **Severity-aware decisions:** @@ -201,21 +206,28 @@ Spawning agents sequentially when they could run in parallel is a protocol viola ### Git flow -Workers signal `RFR` when done. You control commits: -- `LGTM` → worker commits -- Mark a step `- [x]` in the plan file **only when every worker assigned to that step has received LGTM** -- `REVISE` → worker fixes and resubmits with `RFR` +Workers return `signal: rfr` when done. You control commits: +- Send `signal: lgtm` → worker commits +- Mark a step `- [x]` in the plan file **only when every worker assigned to that step has received `signal: lgtm`** +- Send `signal: revise` → worker fixes and resubmits with `signal: rfr` - Merge worktree branches after individual validation - On Tier 2+: merge each worker's branch after validation, resolve conflicts if branches overlap Only the orchestrator updates the plan file. Workers must not modify `.claude/plans/`. -### Review signals +### Message schema -| Signal | Direction | Meaning | +All agent communication uses typed YAML frontmatter envelopes defined in the `message-schema` skill. The `signal` field is your primary routing key. + +| Envelope signal | Direction | Your action | |---|---|---| -| `RFR` | worker → orchestrator | Ready for review | -| `LGTM` | orchestrator → worker | Approved, commit your changes | -| `REVISE` | orchestrator → worker | Fix the listed issues and resubmit | -| `VERDICT: PASS / PASS WITH NOTES / FAIL` | reviewer → orchestrator | Review result | -| `VERDICT: PASS / PARTIAL / FAIL` | auditor → orchestrator | Runtime validation result | +| `signal: rfr` | worker → you | Dispatch to reviewer (+ auditor if risk tags match) | +| `signal: pass` | reviewer/auditor → you | Advance to next wave | +| `signal: pass_with_notes` | reviewer/auditor → you | Advance, surface notes in delivery | +| `signal: fail` | reviewer/auditor → you | Send `revision_request` to worker | +| `signal: triage_complete` | architect → you | Check `research_needed`, spawn researchers or resume architect | +| `signal: plan_complete` | architect → you | Read plan file, begin wave dispatch | +| `signal: research_complete` | researcher → you | Collect, assemble into Research Context | +| `signal: blocked` / `signal: escalate` | any → you | Investigate or escalate to user | + +When dispatching agents, use the orchestrator→agent envelope types (`task_assignment`, `revision_request`, `approval`, `triage_request`, `architecture_request`, `research_request`) from the message-schema skill. diff --git a/skills/qa-checklist/SKILL.md b/skills/qa-checklist/SKILL.md index 50bdc54..844d752 100644 --- a/skills/qa-checklist/SKILL.md +++ b/skills/qa-checklist/SKILL.md @@ -39,9 +39,17 @@ Before returning your output, validate against every item below. If you find a v - If you stated something as fact, can you back it up? Challenge your own claims. - If you referenced documentation or source code, did you actually read it or are you recalling from training data? When it matters, verify. +### Schema compliance +- Does your output start with a valid YAML frontmatter envelope (`---` delimiters)? +- Does the `type` field match your message type? +- Does the `signal` field use a valid enum value from the message-schema skill? +- Are all required fields for your message type present? +- Are hard rules satisfied (e.g., `critical_count > 0` requires `signal: fail`)? + ## After validation +Set `qa_check: pass` or `qa_check: fail` in your frontmatter envelope. This replaces the old `QA self-check` prose line. + In your Self-Assessment section, include: -- `QA self-check: [pass/fail]` — did your output survive the checklist? -- If fail: what you found and fixed before submission +- If qa_check is fail: what you found and fixed before submission - If anything remains unverifiable, flag it explicitly as `Unverified: [claim]` diff --git a/skills/worker-protocol/SKILL.md b/skills/worker-protocol/SKILL.md index c188923..2789162 100644 --- a/skills/worker-protocol/SKILL.md +++ b/skills/worker-protocol/SKILL.md @@ -5,15 +5,28 @@ description: Standard output format, feedback handling, and operational procedur ## Output format -Return using this structure. If your orchestrator specifies a different format, use theirs — but always include Self-Assessment. +Wrap your output in a `worker_submission` envelope per the message-schema skill: + +```yaml +--- +type: worker_submission +signal: rfr | blocked | escalate +files_changed: + - path/to/file1 + - path/to/file2 +ac_coverage: + AC1: pass | fail | partial | na + AC2: pass | fail | partial | na +qa_check: pass | fail +--- +``` + +Then the markdown body: ``` ## Result [Your deliverable here] -## Files Changed -[List files modified/created, or "N/A" if not a code task] - ## Self-Assessment - Acceptance criteria met: [yes/no per criterion, one line each] - Known limitations: [any, or "none"] @@ -37,11 +50,11 @@ Before returning your output, run the `qa-checklist` skill against your work. Fi ## Commits -Do not commit until your orchestrator sends `LGTM`. End your output with `RFR` to signal you're ready for review. +Do not commit until your orchestrator sends `signal: lgtm`. Your output envelope's `signal: rfr` replaces the old freetext `RFR` — the envelope IS the signal. -- `RFR` — you → orchestrator: work complete, ready for review -- `LGTM` — orchestrator → you: approved, commit now -- `REVISE` — orchestrator → you: needs fixes (issues attached) +- `signal: rfr` — you → orchestrator: work complete, ready for review +- `signal: lgtm` — orchestrator → you: approved, commit now +- `signal: revise` — orchestrator → you: needs fixes (issues attached) When you receive `LGTM`: - Commit using conventional commit format per project conventions From b741354dd83c1bdf0ca72e1ae8c99f9ccbcfdf1c Mon Sep 17 00:00:00 2001 From: Bryan Ramos Date: Thu, 2 Apr 2026 07:48:47 -0400 Subject: [PATCH 13/36] fix: resolve critical issues in install.sh, settings.json, and worker-protocol MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - install.sh: replace unreachable $? check with `if !` pattern (set -e exits before the check runs on Windows mklink failure) - settings.json: remove fragile Bash deny patterns that can't match across path separators; broaden .env denies to recursive **/.env with Read/Write/Edit - worker-protocol: align QA instruction with qa-checklist — qa_check goes in frontmatter envelope, not as a prose line --- install.sh | 6 ++---- settings.json | 12 ++++++------ skills/worker-protocol/SKILL.md | 2 +- 3 files changed, 9 insertions(+), 11 deletions(-) diff --git a/install.sh b/install.sh index 50fbf87..3e1a41c 100755 --- a/install.sh +++ b/install.sh @@ -62,8 +62,7 @@ create_symlink() { local win_dst win_src="$(cygpath -w "$src")" win_dst="$(cygpath -w "$dst")" - cmd //c "mklink /D \"$win_dst\" \"$win_src\"" > /dev/null 2>&1 - if [ $? -ne 0 ]; then + if ! cmd //c "mklink /D \"$win_dst\" \"$win_src\"" > /dev/null 2>&1; then echo "ERROR: mklink failed for $name." echo "On Windows, enable Developer Mode (Settings > Update & Security > For Developers)" echo "or run this script as Administrator." @@ -104,8 +103,7 @@ create_file_symlink() { local win_dst win_src="$(cygpath -w "$src")" win_dst="$(cygpath -w "$dst")" - cmd //c "mklink \"$win_dst\" \"$win_src\"" > /dev/null 2>&1 - if [ $? -ne 0 ]; then + if ! cmd //c "mklink \"$win_dst\" \"$win_src\"" > /dev/null 2>&1; then echo "ERROR: mklink failed for $name." echo "On Windows, enable Developer Mode (Settings > Update & Security > For Developers)" echo "or run this script as Administrator." diff --git a/settings.json b/settings.json index ce0414e..3e584c6 100644 --- a/settings.json +++ b/settings.json @@ -19,18 +19,18 @@ "Read(~/.ssh/**)", "Read(~/.aws/**)", "Read(~/.gnupg/**)", - "Read(./.env)", - "Read(./.env.*)", + "Read(**/.env)", + "Read(**/.env.*)", "Write(~/.ssh/**)", "Write(~/.aws/**)", "Write(~/.gnupg/**)", + "Write(**/.env)", + "Write(**/.env.*)", "Edit(~/.ssh/**)", "Edit(~/.aws/**)", "Edit(~/.gnupg/**)", - "Bash(*.ssh/*)", - "Bash(*.aws/*)", - "Bash(*.gnupg/*)", - "Bash(*.env*)" + "Edit(**/.env)", + "Edit(**/.env.*)" ], "ask": [ "Bash(rm *)", diff --git a/skills/worker-protocol/SKILL.md b/skills/worker-protocol/SKILL.md index 2789162..b2a4581 100644 --- a/skills/worker-protocol/SKILL.md +++ b/skills/worker-protocol/SKILL.md @@ -41,7 +41,7 @@ Produce the assigned deliverable. Accurately. Completely. Nothing more. ## Self-QA -Before returning your output, run the `qa-checklist` skill against your work. Fix any issues you find — don't just note them. Your Self-Assessment must include the `QA self-check: pass/fail` line. If you can't pass your own QA, flag what remains and why. +Before returning your output, run the `qa-checklist` skill against your work. Fix any issues you find — don't just note them. Set `qa_check: pass` or `qa_check: fail` in your frontmatter envelope. If you can't pass your own QA, flag what remains and why in your Self-Assessment. ## Cost sensitivity From cf5c14ba298714ccc886eb6aecf61c70e63875c4 Mon Sep 17 00:00:00 2001 From: Bryan Ramos Date: Thu, 2 Apr 2026 07:49:46 -0400 Subject: [PATCH 14/36] fix: resolve moderate issues across config MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - architect: restore effort: max to match orchestrate team roster - orchestrate: remove researcher background annotation (blocks planning), remove overly broad auditor trigger clause, risk-tag table is authoritative - qa-checklist: add auditor hard rules (security_findings, build/test status) - settings.json: simplify .env deny to single **/.env* glob per tool - rules/01-session: clarify persistent context includes .claude/memory/ - memory: rename todo_inter_agent_schema.md → inter_agent_schema.md --- .claude/memory/MEMORY.md | 2 +- ...{todo_inter_agent_schema.md => inter_agent_schema.md} | 0 agents/architect.md | 2 +- rules/01-session.md | 4 ++-- settings.json | 9 +++------ skills/orchestrate/SKILL.md | 4 ++-- skills/qa-checklist/SKILL.md | 4 +++- 7 files changed, 12 insertions(+), 13 deletions(-) rename .claude/memory/{todo_inter_agent_schema.md => inter_agent_schema.md} (100%) diff --git a/.claude/memory/MEMORY.md b/.claude/memory/MEMORY.md index 897a83b..e910cac 100644 --- a/.claude/memory/MEMORY.md +++ b/.claude/memory/MEMORY.md @@ -1 +1 @@ -- [Inter-agent communication schema](todo_inter_agent_schema.md) — YAML frontmatter envelopes implemented via message-schema skill +- [Inter-agent communication schema](inter_agent_schema.md) — YAML frontmatter envelopes implemented via message-schema skill diff --git a/.claude/memory/todo_inter_agent_schema.md b/.claude/memory/inter_agent_schema.md similarity index 100% rename from .claude/memory/todo_inter_agent_schema.md rename to .claude/memory/inter_agent_schema.md diff --git a/agents/architect.md b/agents/architect.md index 996ae0c..141dc3b 100644 --- a/agents/architect.md +++ b/agents/architect.md @@ -2,7 +2,7 @@ name: architect description: Research-first planning agent. Handles triage, research coordination, architecture design, and wave decomposition. Use before any non-trivial implementation task. Produces the implementation blueprint the entire team follows. model: opus -effort: high +effort: max permissionMode: plan tools: Read, Glob, Grep, WebFetch, WebSearch, Bash, Write disallowedTools: Edit diff --git a/rules/01-session.md b/rules/01-session.md index f3db2d6..bbf053a 100644 --- a/rules/01-session.md +++ b/rules/01-session.md @@ -1,8 +1,8 @@ # Session Behavior - Treat each session as stateless — do not assume context from prior sessions -- The CLAUDE.md hierarchy is the only source of persistent context -- If something needs to carry forward across sessions, it belongs in a CLAUDE.md file, not in session memory +- The CLAUDE.md hierarchy and `.claude/memory/` are the only sources of persistent context +- If something needs to carry forward across sessions, persist it in the appropriate file — not in session memory # Project Memory diff --git a/settings.json b/settings.json index 3e584c6..582b9a2 100644 --- a/settings.json +++ b/settings.json @@ -19,18 +19,15 @@ "Read(~/.ssh/**)", "Read(~/.aws/**)", "Read(~/.gnupg/**)", - "Read(**/.env)", - "Read(**/.env.*)", + "Read(**/.env*)", "Write(~/.ssh/**)", "Write(~/.aws/**)", "Write(~/.gnupg/**)", - "Write(**/.env)", - "Write(**/.env.*)", + "Write(**/.env*)", "Edit(~/.ssh/**)", "Edit(~/.aws/**)", "Edit(~/.gnupg/**)", - "Edit(**/.env)", - "Edit(**/.env.*)" + "Edit(**/.env*)" ], "ask": [ "Bash(rm *)", diff --git a/skills/orchestrate/SKILL.md b/skills/orchestrate/SKILL.md index a940ded..9085dde 100644 --- a/skills/orchestrate/SKILL.md +++ b/skills/orchestrate/SKILL.md @@ -13,7 +13,7 @@ You (orchestrator) ├── worker (sonnet default — haiku for trivial, opus for architectural) ├── debugger (sonnet) — bug diagnosis and minimal fixes ├── documenter (sonnet) — documentation only, never touches source - ├── researcher (sonnet, background) — one per topic, parallel fact-finding + ├── researcher (sonnet) — one per topic, parallel fact-finding ├── architect (opus, effort: max) — triage, research coordination, architecture, wave decomposition ├── reviewer (sonnet) — code quality + AC verification + claim checking └── auditor (sonnet, background) — security analysis + runtime validation @@ -104,7 +104,7 @@ For each wave in the plan: After each wave, spawn `reviewer` and `auditor` in a single response. They run in parallel. - **Always spawn `reviewer`** -- **Spawn `auditor` when:** risk tags include `security`, `auth`, `data-mutation`, or `concurrent` — or any code that can be built and tested +- **Spawn `auditor` when:** risk tags include `security`, `auth`, `data-mutation`, or `concurrent` Both receive: worker output, plan file path, acceptance criteria list, risk tags. diff --git a/skills/qa-checklist/SKILL.md b/skills/qa-checklist/SKILL.md index 844d752..fb4caec 100644 --- a/skills/qa-checklist/SKILL.md +++ b/skills/qa-checklist/SKILL.md @@ -44,7 +44,9 @@ Before returning your output, validate against every item below. If you find a v - Does the `type` field match your message type? - Does the `signal` field use a valid enum value from the message-schema skill? - Are all required fields for your message type present? -- Are hard rules satisfied (e.g., `critical_count > 0` requires `signal: fail`)? +- Are hard rules satisfied? + - `review_verdict`: `critical_count > 0` requires `signal: fail` + - `audit_verdict`: `security_findings.critical > 0` or `build_status: fail` or `test_status: fail` requires `signal: fail` ## After validation From 3c3ba420696659d1ea5911b2dfc0dca3ad25a2bf Mon Sep 17 00:00:00 2001 From: Bryan Ramos Date: Thu, 2 Apr 2026 07:50:43 -0400 Subject: [PATCH 15/36] chore: resolve minor issues across config - Add when_to_use to worker-protocol, qa-checklist, and project skills - architect: allow Write for plan files, add NotebookEdit to disallowedTools - documenter: remove redundant permissionMode (not enforced at runtime) - orchestrate: split blocked/escalate into explicit routing rows, distinguish architect plan_result blocked from generic blocked --- agents/architect.md | 2 +- agents/documenter.md | 1 - skills/orchestrate/SKILL.md | 3 ++- skills/project/SKILL.md | 1 + skills/qa-checklist/SKILL.md | 1 + skills/worker-protocol/SKILL.md | 1 + 6 files changed, 6 insertions(+), 3 deletions(-) diff --git a/agents/architect.md b/agents/architect.md index 141dc3b..665029d 100644 --- a/agents/architect.md +++ b/agents/architect.md @@ -5,7 +5,7 @@ model: opus effort: max permissionMode: plan tools: Read, Glob, Grep, WebFetch, WebSearch, Bash, Write -disallowedTools: Edit +disallowedTools: Edit, NotebookEdit maxTurns: 35 skills: - conventions diff --git a/agents/documenter.md b/agents/documenter.md index da5a203..9e578b0 100644 --- a/agents/documenter.md +++ b/agents/documenter.md @@ -4,7 +4,6 @@ description: Use when asked to write or update documentation — READMEs, API re model: sonnet effort: high memory: project -permissionMode: acceptEdits tools: Read, Write, Edit, Glob, Grep, Bash maxTurns: 20 skills: diff --git a/skills/orchestrate/SKILL.md b/skills/orchestrate/SKILL.md index 9085dde..b789b0d 100644 --- a/skills/orchestrate/SKILL.md +++ b/skills/orchestrate/SKILL.md @@ -228,6 +228,7 @@ All agent communication uses typed YAML frontmatter envelopes defined in the `me | `signal: triage_complete` | architect → you | Check `research_needed`, spawn researchers or resume architect | | `signal: plan_complete` | architect → you | Read plan file, begin wave dispatch | | `signal: research_complete` | researcher → you | Collect, assemble into Research Context | -| `signal: blocked` / `signal: escalate` | any → you | Investigate or escalate to user | +| `signal: blocked` | architect/worker → you | If from `plan_result`: escalate to user before dispatching workers. Otherwise: investigate or unblock. | +| `signal: escalate` | any → you | Escalate to user with context | When dispatching agents, use the orchestrator→agent envelope types (`task_assignment`, `revision_request`, `approval`, `triage_request`, `architecture_request`, `research_request`) from the message-schema skill. diff --git a/skills/project/SKILL.md b/skills/project/SKILL.md index 83fa1a0..b11876d 100644 --- a/skills/project/SKILL.md +++ b/skills/project/SKILL.md @@ -1,6 +1,7 @@ --- name: project description: Instructs agents to check for and ingest a project-specific skill file before starting work. +when_to_use: Loaded by all agents. Checks for .claude/skills/project.md in the working directory and ingests project-local conventions. --- Before starting any work, check for a project-specific skill file at `.claude/skills/project.md` in the current working directory. diff --git a/skills/qa-checklist/SKILL.md b/skills/qa-checklist/SKILL.md index fb4caec..3ebcf34 100644 --- a/skills/qa-checklist/SKILL.md +++ b/skills/qa-checklist/SKILL.md @@ -1,6 +1,7 @@ --- name: qa-checklist description: Self-validation checklist. All workers run this against their own output before returning results. +when_to_use: Loaded by all agents that produce output envelopes. Run before returning results to validate factual accuracy, scope, security, and schema compliance. --- ## Self-QA checklist diff --git a/skills/worker-protocol/SKILL.md b/skills/worker-protocol/SKILL.md index b2a4581..4a73b6f 100644 --- a/skills/worker-protocol/SKILL.md +++ b/skills/worker-protocol/SKILL.md @@ -1,6 +1,7 @@ --- name: worker-protocol description: Standard output format, feedback handling, and operational procedures for all worker agents. +when_to_use: Loaded by worker, debugger, and documenter agents. Defines the worker_submission envelope format and commit workflow. --- ## Output format From f5c417dfd01efaefbe56bdc74da16b3e5e7a41fc Mon Sep 17 00:00:00 2001 From: Bryan Ramos Date: Thu, 2 Apr 2026 07:51:23 -0400 Subject: [PATCH 16/36] fix(architect): allow Edit for plan amendments, keep only NotebookEdit blocked --- agents/architect.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/agents/architect.md b/agents/architect.md index 665029d..14096a7 100644 --- a/agents/architect.md +++ b/agents/architect.md @@ -5,7 +5,7 @@ model: opus effort: max permissionMode: plan tools: Read, Glob, Grep, WebFetch, WebSearch, Bash, Write -disallowedTools: Edit, NotebookEdit +disallowedTools: NotebookEdit maxTurns: 35 skills: - conventions From 1ee8dcdfddc956a9683196234ae4d51a255749d6 Mon Sep 17 00:00:00 2001 From: Bryan Ramos Date: Thu, 2 Apr 2026 07:51:46 -0400 Subject: [PATCH 17/36] =?UTF-8?q?fix(architect):=20remove=20unnecessary=20?= =?UTF-8?q?disallowedTools=20=E2=80=94=20tool=20list=20is=20the=20boundary?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- agents/architect.md | 1 - 1 file changed, 1 deletion(-) diff --git a/agents/architect.md b/agents/architect.md index 14096a7..9c4cfb5 100644 --- a/agents/architect.md +++ b/agents/architect.md @@ -5,7 +5,6 @@ model: opus effort: max permissionMode: plan tools: Read, Glob, Grep, WebFetch, WebSearch, Bash, Write -disallowedTools: NotebookEdit maxTurns: 35 skills: - conventions From 76f7f16eff3ace8b5fde13031417561fff85496d Mon Sep 17 00:00:00 2001 From: Bryan Ramos Date: Thu, 2 Apr 2026 07:58:41 -0400 Subject: [PATCH 18/36] fix: resolve review round 2 findings - CLAUDE.md: add missing message-schema and project to skill list - debugger, documenter: add qa-checklist to skills (worker-protocol references it) - message-schema: make moderate_count/minor_count required on review_verdict, make has_blockers required on plan_result, remove dangling format field - orchestrate: split blocked routing by type (plan_result vs worker_submission) - worker-protocol: remove stale migration note about freetext RFR - qa-checklist: add plan_result has_blockers hard rule - architect: remove dangling format field from plan_result envelope --- CLAUDE.md | 2 +- agents/architect.md | 1 - agents/debugger.md | 1 + agents/documenter.md | 1 + skills/message-schema/SKILL.md | 8 +++----- skills/orchestrate/SKILL.md | 3 ++- skills/qa-checklist/SKILL.md | 1 + skills/worker-protocol/SKILL.md | 2 +- 8 files changed, 10 insertions(+), 9 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index ea55f37..1e2879f 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,4 +1,4 @@ # Global Claude Code Instructions Rules are modularized in `~/.claude/rules/` and loaded automatically. -Agent-team specific protocols live in skills (orchestrate, conventions, worker-protocol, qa-checklist). +Agent-team specific protocols live in skills (orchestrate, conventions, worker-protocol, qa-checklist, message-schema, project). diff --git a/agents/architect.md b/agents/architect.md index 9c4cfb5..6256132 100644 --- a/agents/architect.md +++ b/agents/architect.md @@ -104,7 +104,6 @@ After writing the plan file, return a `plan_result` envelope: type: plan_result signal: plan_complete | blocked plan_file: .claude/plans/kebab-case-title.md -format: brief | full wave_count: 3 step_count: 7 risk_tags: diff --git a/agents/debugger.md b/agents/debugger.md index 329fd5c..33fb7bc 100644 --- a/agents/debugger.md +++ b/agents/debugger.md @@ -9,6 +9,7 @@ skills: - conventions - worker-protocol - message-schema + - qa-checklist - project --- diff --git a/agents/documenter.md b/agents/documenter.md index 9e578b0..b68a41e 100644 --- a/agents/documenter.md +++ b/agents/documenter.md @@ -10,6 +10,7 @@ skills: - conventions - worker-protocol - message-schema + - qa-checklist - project --- diff --git a/skills/message-schema/SKILL.md b/skills/message-schema/SKILL.md index 63f993f..626bf52 100644 --- a/skills/message-schema/SKILL.md +++ b/skills/message-schema/SKILL.md @@ -90,8 +90,7 @@ ac_coverage: --- ``` -Required: `type`, `signal`, `critical_count`, `ac_coverage` -Optional: `moderate_count`, `minor_count` +Required: `type`, `signal`, `critical_count`, `moderate_count`, `minor_count`, `ac_coverage` **Hard rule:** `critical_count > 0` requires `signal: fail`. @@ -153,7 +152,6 @@ Emitted by: architect (Phase 2) type: plan_result signal: plan_complete | blocked plan_file: .claude/plans/kebab-case-title.md -format: brief | full wave_count: 3 step_count: 7 risk_tags: @@ -163,8 +161,8 @@ has_blockers: false --- ``` -Required: `type`, `signal`, `plan_file`, `wave_count`, `risk_tags` -Optional: `format`, `step_count`, `has_blockers` +Required: `type`, `signal`, `plan_file`, `wave_count`, `risk_tags`, `has_blockers` +Optional: `step_count` **Routing:** `has_blockers: true` triggers user escalation before worker dispatch. diff --git a/skills/orchestrate/SKILL.md b/skills/orchestrate/SKILL.md index b789b0d..ac88039 100644 --- a/skills/orchestrate/SKILL.md +++ b/skills/orchestrate/SKILL.md @@ -228,7 +228,8 @@ All agent communication uses typed YAML frontmatter envelopes defined in the `me | `signal: triage_complete` | architect → you | Check `research_needed`, spawn researchers or resume architect | | `signal: plan_complete` | architect → you | Read plan file, begin wave dispatch | | `signal: research_complete` | researcher → you | Collect, assemble into Research Context | -| `signal: blocked` | architect/worker → you | If from `plan_result`: escalate to user before dispatching workers. Otherwise: investigate or unblock. | +| `signal: blocked` (`plan_result`) | architect → you | Escalate to user before dispatching workers | +| `signal: blocked` (`worker_submission`) | worker → you | Investigate blocker, unblock or reassign | | `signal: escalate` | any → you | Escalate to user with context | When dispatching agents, use the orchestrator→agent envelope types (`task_assignment`, `revision_request`, `approval`, `triage_request`, `architecture_request`, `research_request`) from the message-schema skill. diff --git a/skills/qa-checklist/SKILL.md b/skills/qa-checklist/SKILL.md index 3ebcf34..045e02f 100644 --- a/skills/qa-checklist/SKILL.md +++ b/skills/qa-checklist/SKILL.md @@ -48,6 +48,7 @@ Before returning your output, validate against every item below. If you find a v - Are hard rules satisfied? - `review_verdict`: `critical_count > 0` requires `signal: fail` - `audit_verdict`: `security_findings.critical > 0` or `build_status: fail` or `test_status: fail` requires `signal: fail` + - `plan_result`: `has_blockers: true` requires orchestrator escalation to user before worker dispatch ## After validation diff --git a/skills/worker-protocol/SKILL.md b/skills/worker-protocol/SKILL.md index 4a73b6f..e879158 100644 --- a/skills/worker-protocol/SKILL.md +++ b/skills/worker-protocol/SKILL.md @@ -51,7 +51,7 @@ Before returning your output, run the `qa-checklist` skill against your work. Fi ## Commits -Do not commit until your orchestrator sends `signal: lgtm`. Your output envelope's `signal: rfr` replaces the old freetext `RFR` — the envelope IS the signal. +Do not commit until your orchestrator sends `signal: lgtm`. - `signal: rfr` — you → orchestrator: work complete, ready for review - `signal: lgtm` — orchestrator → you: approved, commit now From 7fdbb4065609f31eea2e59f6a56b17196267c6b0 Mon Sep 17 00:00:00 2001 From: Bryan Ramos Date: Thu, 2 Apr 2026 08:03:31 -0400 Subject: [PATCH 19/36] fix: resolve review round 3 findings - reviewer, auditor: add qa-checklist to skills (both produce envelopes) - worker-protocol: annotate ac_coverage as optional in envelope template - message-schema: document that security_findings.high is non-blocking - qa-checklist: reframe plan_result has_blockers as intent confirmation --- agents/auditor.md | 1 + agents/reviewer.md | 1 + skills/message-schema/SKILL.md | 2 +- skills/qa-checklist/SKILL.md | 2 +- skills/worker-protocol/SKILL.md | 2 +- 5 files changed, 5 insertions(+), 3 deletions(-) diff --git a/agents/auditor.md b/agents/auditor.md index 1aabe70..fd9e414 100644 --- a/agents/auditor.md +++ b/agents/auditor.md @@ -9,6 +9,7 @@ maxTurns: 25 skills: - conventions - message-schema + - qa-checklist - project --- diff --git a/agents/reviewer.md b/agents/reviewer.md index 74edf29..05d2b3a 100644 --- a/agents/reviewer.md +++ b/agents/reviewer.md @@ -9,6 +9,7 @@ maxTurns: 20 skills: - conventions - message-schema + - qa-checklist - project --- diff --git a/skills/message-schema/SKILL.md b/skills/message-schema/SKILL.md index 626bf52..585a7f3 100644 --- a/skills/message-schema/SKILL.md +++ b/skills/message-schema/SKILL.md @@ -118,7 +118,7 @@ typecheck_status: pass | fail | skipped Required: `type`, `signal`, `security_findings`, `build_status`, `test_status` Optional: `typecheck_status` -**Hard rule:** `security_findings.critical > 0` or `build_status: fail` or `test_status: fail` requires `signal: fail`. +**Hard rule:** `security_findings.critical > 0` or `build_status: fail` or `test_status: fail` requires `signal: fail`. High-severity findings (`security_findings.high > 0`) do not require `fail` — use `pass_with_notes`. Body: Security findings by severity (or CLEAN), then Runtime section with tested/passed/failed. diff --git a/skills/qa-checklist/SKILL.md b/skills/qa-checklist/SKILL.md index 045e02f..73eb09b 100644 --- a/skills/qa-checklist/SKILL.md +++ b/skills/qa-checklist/SKILL.md @@ -48,7 +48,7 @@ Before returning your output, validate against every item below. If you find a v - Are hard rules satisfied? - `review_verdict`: `critical_count > 0` requires `signal: fail` - `audit_verdict`: `security_findings.critical > 0` or `build_status: fail` or `test_status: fail` requires `signal: fail` - - `plan_result`: `has_blockers: true` requires orchestrator escalation to user before worker dispatch + - `plan_result`: if you set `has_blockers: true`, confirm this is intentional — it triggers user escalation before worker dispatch ## After validation diff --git a/skills/worker-protocol/SKILL.md b/skills/worker-protocol/SKILL.md index e879158..4b7dbb9 100644 --- a/skills/worker-protocol/SKILL.md +++ b/skills/worker-protocol/SKILL.md @@ -15,7 +15,7 @@ signal: rfr | blocked | escalate files_changed: - path/to/file1 - path/to/file2 -ac_coverage: +ac_coverage: # optional — omit when no AC provided AC1: pass | fail | partial | na AC2: pass | fail | partial | na qa_check: pass | fail From 8d08f9650c233d663b791bf78aa54b5b747dbcd6 Mon Sep 17 00:00:00 2001 From: Bryan Ramos Date: Thu, 2 Apr 2026 08:04:44 -0400 Subject: [PATCH 20/36] docs(README): clarify manual memory vs agent memory systems --- README.md | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 3424b74..6ca7fb7 100644 --- a/README.md +++ b/README.md @@ -69,6 +69,11 @@ Each project repo can extend the team with local config in `.claude/`: Commit `.claude/` with the project so the team has context wherever it runs. -## Agent memory +## Memory -Agents with `memory: project` scope write persistent memory to `.claude/agent-memory/` in the project directory. This memory is project-scoped and can be committed with the repo so future sessions pick up where prior ones left off. +Two memory systems coexist: + +- **Manual memory** (`.claude/memory/`) — curated context files with YAML frontmatter, indexed by `MEMORY.md`. Loaded as part of the CLAUDE.md hierarchy on every session. Use this for project decisions, user preferences, and reference pointers. +- **Agent memory** (`.claude/agent-memory/`) — Claude Code's built-in runtime memory, written automatically by agents with `memory: project` scope. Excluded from CLAUDE.md context via `claudeMdExcludes` to avoid polluting the context window. + +Commit both directories with the repo so memory persists across machines and sessions. From d812c7f49a267d731895bce2456389a21c499202 Mon Sep 17 00:00:00 2001 From: Bryan Ramos Date: Thu, 2 Apr 2026 08:28:29 -0400 Subject: [PATCH 21/36] feat: add Codex CLI compatibility layer - flake.nix: devShell with yq-go for config generation - generate-codex.sh: generates Codex agent TOML, AGENTS.md, and config.toml from Claude source files (idempotent, yq-powered) - install.sh: optional Codex symlinks when ~/.codex exists (skills shared via ~/.agents/skills/, agents/config/AGENTS.md to ~/.codex/) - .gitignore: exclude generated codex/ directory - README.md: document Codex compatibility setup and model mapping --- .gitignore | 3 + README.md | 29 ++++++++ flake.lock | 27 +++++++ flake.nix | 14 ++++ generate-codex.sh | 184 ++++++++++++++++++++++++++++++++++++++++++++++ install.sh | 30 ++++++++ 6 files changed, 287 insertions(+) create mode 100644 flake.lock create mode 100644 flake.nix create mode 100755 generate-codex.sh diff --git a/.gitignore b/.gitignore index a3cadf6..0da7350 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,6 @@ settings.local.json # OS noise .DS_Store Thumbs.db + +# Generated Codex CLI config (derived from Claude source files via generate-codex.sh) +codex/ diff --git a/README.md b/README.md index 6ca7fb7..c8ab5c0 100644 --- a/README.md +++ b/README.md @@ -59,6 +59,35 @@ For simple tasks, agents can be invoked directly: /agent worker Fix the broken pagination in the user list endpoint ``` +## Codex CLI compatibility + +This project also generates configuration for [OpenAI Codex CLI](https://github.com/openai/codex). Claude Code config is the source of truth; Codex config is derived from it. + +### Setup + +```bash +nix develop # enter devShell with yq +./generate-codex.sh # generate Codex config from Claude source files +./install.sh # installs both Claude and Codex (if ~/.codex exists) +``` + +### What gets generated + +| Source | Generated | Codex location | +|---|---|---| +| `agents/*.md` | `codex/agents/*.toml` | `~/.codex/agents/` | +| `CLAUDE.md` + `rules/*.md` | `codex/AGENTS.md` | `~/.codex/AGENTS.md` | +| `settings.json` | `codex/config.toml` | `~/.codex/config.toml` | +| `skills/` | (shared as-is) | `~/.agents/skills/` | + +### Model mapping + +| Claude Code | Codex CLI | +|---|---| +| `opus` | `o3` | +| `sonnet` | `o4-mini` | +| `haiku` | `o4-mini` | + ## Project-specific config Each project repo can extend the team with local config in `.claude/`: diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000..6b92e8a --- /dev/null +++ b/flake.lock @@ -0,0 +1,27 @@ +{ + "nodes": { + "nixpkgs": { + "locked": { + "lastModified": 1775095191, + "narHash": "sha256-CsqRiYbgQyv01LS0NlC7shwzhDhjNDQSrhBX8VuD3nM=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "106eb93cbb9d4e4726bf6bc367a3114f7ed6b32f", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixpkgs-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "nixpkgs": "nixpkgs" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..d07f69e --- /dev/null +++ b/flake.nix @@ -0,0 +1,14 @@ +{ + inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable"; + outputs = { nixpkgs, ... }: + let + systems = [ "x86_64-linux" "aarch64-linux" "x86_64-darwin" "aarch64-darwin" ]; + forAllSystems = f: nixpkgs.lib.genAttrs systems (system: f nixpkgs.legacyPackages.${system}); + in { + devShells = forAllSystems (pkgs: { + default = pkgs.mkShell { + packages = [ pkgs.yq-go ]; + }; + }); + }; +} diff --git a/generate-codex.sh b/generate-codex.sh new file mode 100755 index 0000000..8bf99a2 --- /dev/null +++ b/generate-codex.sh @@ -0,0 +1,184 @@ +#!/usr/bin/env bash +set -euo pipefail + +# generate-codex.sh — generates Codex CLI config from Claude source files. +# Claude source files are the source of truth; this script derives Codex equivalents. +# Idempotent: safe to run multiple times. + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +CODEX_DIR="$SCRIPT_DIR/codex" +CODEX_AGENTS_DIR="$CODEX_DIR/agents" +AGENTS_SRC="$SCRIPT_DIR/agents" +RULES_DIR="$SCRIPT_DIR/rules" +CLAUDE_MD="$SCRIPT_DIR/CLAUDE.md" +SETTINGS_JSON="$SCRIPT_DIR/settings.json" + +# Create output directories +mkdir -p "$CODEX_DIR" +mkdir -p "$CODEX_AGENTS_DIR" + +# Clean existing generated agent TOMLs +rm -f "$CODEX_AGENTS_DIR"/*.toml + +# --------------------------------------------------------------------------- +# map_model — maps Claude model name to Codex model name +# --------------------------------------------------------------------------- +map_model() { + local model="$1" + case "$model" in + opus) echo "o3" ;; + sonnet) echo "o4-mini" ;; + haiku) echo "o4-mini" ;; + *) echo "o4-mini" ;; + esac +} + +# --------------------------------------------------------------------------- +# map_effort — maps Claude effort level to Codex model_reasoning_effort +# --------------------------------------------------------------------------- +map_effort() { + local effort="$1" + case "$effort" in + low) echo "low" ;; + medium) echo "medium" ;; + high) echo "high" ;; + max) echo "xhigh" ;; + *) echo "medium" ;; + esac +} + +# --------------------------------------------------------------------------- +# map_sandbox_mode — determines Codex sandbox_mode from agent frontmatter +# $1 = permissionMode value (plan / acceptEdits / "") +# $2 = tools list (comma-separated) +# --------------------------------------------------------------------------- +map_sandbox_mode() { + local permission_mode="$1" + local tools="$2" + + # plan mode is read-only + if [ "$permission_mode" = "plan" ]; then + echo "read-only" + return + fi + + # acceptEdits with Write or Edit tool → workspace-write + if [ "$permission_mode" = "acceptEdits" ]; then + if echo "$tools" | grep -qE '\b(Write|Edit)\b'; then + echo "workspace-write" + return + fi + fi + + # Default: read-only + echo "read-only" +} + +# --------------------------------------------------------------------------- +# generate_agent_toml — converts a single agent .md file to Codex .toml +# --------------------------------------------------------------------------- +generate_agent_toml() { + local src_file="$1" + local agent_basename + agent_basename="$(basename "$src_file" .md)" + local dst_file="$CODEX_AGENTS_DIR/${agent_basename}.toml" + + # Extract YAML frontmatter using yq + local frontmatter + frontmatter="$(yq --front-matter=extract '.' "$src_file")" + + # Extract individual fields from frontmatter + local name description model effort permission_mode tools disallowed_tools + name="$(echo "$frontmatter" | yq '.name // ""')" + description="$(echo "$frontmatter" | yq '.description // ""')" + model="$(echo "$frontmatter" | yq '.model // ""')" + effort="$(echo "$frontmatter" | yq '.effort // ""')" + permission_mode="$(echo "$frontmatter" | yq '.permissionMode // ""')" + tools="$(echo "$frontmatter" | yq '.tools // ""')" + disallowed_tools="$(echo "$frontmatter" | yq '.disallowedTools // ""')" + + # Map to Codex equivalents + local codex_model codex_effort codex_sandbox + codex_model="$(map_model "$model")" + codex_effort="$(map_effort "${effort:-medium}")" + codex_sandbox="$(map_sandbox_mode "$permission_mode" "$tools")" + + # Extract markdown body (everything after the closing frontmatter ---) + # The frontmatter block starts at line 1 with --- and ends at the second --- + local body + body="$(awk 'BEGIN{fm=0} /^---$/{if(fm==0){fm=1;next} if(fm==1){fm=2;next}} fm==2{print}' "$src_file")" + + # Build developer_instructions: append disallowedTools note if present + local developer_instructions + developer_instructions="$body" + if [ -n "$disallowed_tools" ] && [ "$disallowed_tools" != "null" ]; then + developer_instructions="${developer_instructions} + +You do NOT have access to these tools: ${disallowed_tools}" + fi + + # Write TOML output + cat > "$dst_file" < "$CODEX_DIR/AGENTS.md" +echo "Generated: $CODEX_DIR/AGENTS.md" + +# --------------------------------------------------------------------------- +# Generate config.toml — derive sandbox_mode from settings.json defaultMode +# --------------------------------------------------------------------------- +echo "" +echo "Generating codex/config.toml..." + +default_mode="$(yq -r '.permissions.defaultMode // "acceptEdits"' "$SETTINGS_JSON")" + +# Map Claude defaultMode to Codex sandbox_mode +case "$default_mode" in + plan) config_sandbox="read-only" ;; + acceptEdits) config_sandbox="workspace-write" ;; + *) config_sandbox="workspace-write" ;; +esac + +cat > "$CODEX_DIR/config.toml" < Date: Thu, 2 Apr 2026 08:29:15 -0400 Subject: [PATCH 22/36] feat(flake): add codex CLI to devShell --- flake.nix | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flake.nix b/flake.nix index d07f69e..c3c4b14 100644 --- a/flake.nix +++ b/flake.nix @@ -7,7 +7,7 @@ in { devShells = forAllSystems (pkgs: { default = pkgs.mkShell { - packages = [ pkgs.yq-go ]; + packages = [ pkgs.yq-go pkgs.codex ]; }; }); }; From b9d8b03895a4b7267ebf686405aff3ec1e8ddb39 Mon Sep 17 00:00:00 2001 From: Bryan Ramos Date: Thu, 2 Apr 2026 08:51:00 -0400 Subject: [PATCH 23/36] feat: template-based dual-target generator for Claude + Codex Replace generate-codex.sh with unified generate.sh that produces both claude/ and codex/ output from template source files. Agent bodies use ${PLANS_DIR}, ${WEB_SEARCH}, ${SEARCH_TOOLS} placeholders expanded per-target via envsubst. Skills and rules made tool-agnostic (no Claude tool names or .claude/ paths). Orchestrate skill stays Claude-only. install.sh now symlinks from claude/agents/ instead of agents/ directly. flake.nix adds gettext (envsubst) to devShell. --- .../template-based-dual-target-generator.md | 338 ++++++++++++++++++ .gitignore | 3 +- README.md | 29 +- agents/architect.md | 4 +- agents/debugger.md | 2 +- agents/documenter.md | 2 +- agents/reviewer.md | 2 +- flake.nix | 2 +- generate-codex.sh | 184 ---------- generate.sh | 295 +++++++++++++++ install.sh | 19 +- rules/01-session.md | 6 +- rules/04-tools.md | 2 +- skills/message-schema/SKILL.md | 4 +- skills/project/SKILL.md | 2 +- skills/qa-checklist/SKILL.md | 2 +- skills/worker-protocol/SKILL.md | 2 +- 17 files changed, 687 insertions(+), 211 deletions(-) create mode 100644 .claude/plans/template-based-dual-target-generator.md delete mode 100755 generate-codex.sh create mode 100755 generate.sh diff --git a/.claude/plans/template-based-dual-target-generator.md b/.claude/plans/template-based-dual-target-generator.md new file mode 100644 index 0000000..15c2636 --- /dev/null +++ b/.claude/plans/template-based-dual-target-generator.md @@ -0,0 +1,338 @@ +--- +date: 2026-04-02 +task: template-based dual-target generator +tier: 2 +status: active +--- + +## Plan: Template-based dual-target generator + +## Summary + +Refactor agent-team from "Claude source of truth with a Codex converter" to "tool-agnostic templates with a generator that produces both Claude and Codex output." Agent bodies gain `${VAR}` placeholders for tool-specific references. Skills and rules are made tool-agnostic by replacing Claude-specific tool names and paths with generic language. A new `generate.sh` replaces `generate-codex.sh` and produces both `claude/` and `codex/` output directories. `install.sh` changes to symlink from the generated `claude/` directory. The orchestrate skill stays Claude-only since it is deeply tied to Claude's Agent tool dispatch model. + +## Out of Scope + +- Changing agent frontmatter schema (YAML fields stay as-is; the generator handles YAML-to-TOML conversion) +- Adding new agents or skills +- Changing the orchestrate skill's content (it stays Claude-only, not templated) +- Changing conventions skill content (already tool-agnostic) +- Modifying the message-schema envelope format +- Codex config.toml generation (the current `generate-codex.sh` logic for mapping model/effort/sandbox is preserved, not redesigned) +- README content updates beyond what's needed to reflect the new structure + +## Research Findings + +**envsubst scoping:** `envsubst '${PLANS_DIR} ${WEB_SEARCH}'` substitutes only the listed variables, leaving other `$` references untouched. Safe for use in files with YAML frontmatter and bash-like content. + +**Codex path conventions:** Codex has no `.claude/plans/` equivalent -- plans are regular files at `plans/` (project-relative). No `.claude/memory/` -- use `memory/` or omit. Skills are discovered from `~/.agents/skills/` via `SKILL.md` auto-matching or `skills.config` in agent TOML. Skills are NOT inlined into `developer_instructions`. + +**Skill strategy:** Option B (make skills tool-agnostic) for most skills. Orchestrate stays Claude-only. Tool name references like "Use Read/Glob/Grep" add marginal value and can be replaced with generic language like "Search the codebase." + +## Codebase Analysis + +### Files to modify + +| File | Change | +|---|---| +| `agents/architect.md` | Replace `.claude/plans/` (lines 19, 106) with `${PLANS_DIR}` | +| `agents/reviewer.md` | Replace `via WebFetch/WebSearch` (line 33) with `${WEB_SEARCH}` | +| `agents/debugger.md` | Replace `Use Grep` (line 25) with `${SEARCH_TOOLS}` | +| `agents/documenter.md` | Replace `Use Read/Glob/Grep` (line 29) with `${SEARCH_TOOLS}` | +| `skills/message-schema/SKILL.md` | Replace `.claude/plans/` (lines 155, 205) with `${PLANS_DIR}` | +| `skills/project/SKILL.md` | Replace `.claude/skills/project.md` (lines 7, 9) with `${PROJECT_SKILL_PATH}` | +| `skills/worker-protocol/SKILL.md` | Replace `Read/Glob/Grep` (line 50) with generic language | +| `skills/qa-checklist/SKILL.md` | Replace `Read/Grep` (line 13) with generic language | +| `rules/01-session.md` | Replace `.claude/memory/` (lines 3, 9, 12) with `${MEMORY_DIR}` | +| `rules/04-tools.md` | Replace `suggest /clear` (line 17) with `${CLEAR_CMD}` | +| `generate-codex.sh` | **Delete** -- replaced by `generate.sh` | +| `generate.sh` | **New** -- produces both `claude/` and `codex/` | +| `install.sh` | Rewire Claude symlinks to point at `claude/` output directory | +| `.gitignore` | Add `claude/` to exclusions | +| `flake.nix` | Add `envsubst` (from `gettext`) to devShell packages | + +### Files for context (read-only) + +| File | Why | +|---|---| +| `agents/worker.md` | Confirm no Claude-specific references in body (clean) | +| `agents/auditor.md` | Confirm no Claude-specific references in body (clean) | +| `agents/researcher.md` | Confirm no Claude-specific references in body (clean) | +| `skills/orchestrate/SKILL.md` | Confirm Claude-only decision (deeply coupled) | +| `skills/conventions/SKILL.md` | Confirm already tool-agnostic (clean) | +| `rules/02-responses.md` through `rules/07-research.md` | Confirm no Claude-specific references (clean, except 04-tools.md) | +| `codex/config.toml` | Understand current generated output (reference only) | + +### Current patterns + +- **Shell scripts** use `set -euo pipefail`, `SCRIPT_DIR` idiom, echo-based progress reporting +- **yq** (yq-go) is the YAML/JSON processor -- used for frontmatter extraction and settings parsing +- **Generated output** is committed to `codex/` but gitignored; `claude/` will follow the same pattern +- **Symlink strategy** in install.sh: directory symlinks for agents/skills/rules, file symlinks for individual config files; backup-on-conflict pattern +- **Agent markdown** uses YAML frontmatter with specific schema fields (`name`, `description`, `model`, `effort`, `permissionMode`, `tools`, `disallowedTools`, `maxTurns`, `skills`, `isolation`, `background`, `memory`) + +## Interface Contracts + +### Module ownership + +- `generate.sh`: owned by Step 5 (Wave 3), responsible for template expansion + output generation +- `agents/*.md` templates: owned by Steps 1-2 (Wave 1), responsible for adding `${VAR}` placeholders +- `skills/` tool-agnostic edits: owned by Step 3 (Wave 1), responsible for removing tool-specific language +- `rules/` tool-agnostic edits: owned by Step 4 (Wave 1), responsible for removing tool-specific paths +- `install.sh`: owned by Step 6 (Wave 3), responsible for rewiring symlink sources +- `.gitignore` + `flake.nix`: owned by Step 7 (Wave 2), support infrastructure + +### Shared interfaces + +**Template variable contract** -- all workers must use exactly these variable names and nothing else: + +```bash +# Variable definitions used by generate.sh for envsubst +# Claude target +PLANS_DIR=".claude/plans" +WEB_SEARCH="via WebFetch/WebSearch" +SEARCH_TOOLS="Use Grep/Glob/Read" +CLEAR_CMD="suggest /clear" +MEMORY_DIR=".claude/memory" +PROJECT_SKILL_PATH=".claude/skills/project.md" + +# Codex target +PLANS_DIR="plans" +WEB_SEARCH="via web search" +SEARCH_TOOLS="Search the codebase" +CLEAR_CMD="suggest starting a new session" +MEMORY_DIR="memory" +PROJECT_SKILL_PATH=".agents/skills/project/SKILL.md" +``` + +**Agent body extraction pattern** (preserved from `generate-codex.sh`): +```bash +# Extract body after closing --- of frontmatter +awk 'BEGIN{fm=0} /^---$/{if(fm==0){fm=1;next} if(fm==1){fm=2;next}} fm==2{print}' "$src_file" +``` + +**Claude output directory structure:** +``` +claude/ +├── agents/ # Expanded .md files (frontmatter preserved, body substituted) +├── CLAUDE.md # Copied from source +├── settings.json # Copied from source +├── rules -> ../rules # Symlink to shared rules (already tool-agnostic after Wave 1) +└── skills -> ../skills # Symlink to shared skills (already tool-agnostic after Wave 1) +``` + +**Codex output directory structure:** +``` +codex/ +├── agents/ # Generated .toml files (body substituted with Codex values) +├── AGENTS.md # Generated from CLAUDE.md + expanded rules +└── config.toml # Generated from settings.json +``` + +### Conventions for this task + +- Error handling: `set -euo pipefail` in all shell scripts. Echo progress for each major operation. Non-zero exit on failure. +- Naming: `${UPPER_SNAKE_CASE}` for template variables. `kebab-case` for file names. +- Template markers: Use `${VAR}` syntax only. Never use `$VAR` (ambiguous with shell) or `{{VAR}}` (not envsubst-compatible). +- Skill/rule edits: Replace tool-specific language with generic equivalents. Do NOT add `${VAR}` placeholders to skills or rules -- they are made tool-agnostic directly, not templated. Only agent bodies and message-schema (which references plan paths in examples) use template variables. + +**Correction on skill/rule strategy:** Skills and rules become tool-agnostic by direct edit (hardcoded generic language). They are then shared as-is between both targets via symlinks from the output directories. Template variables (`${VAR}`) are used ONLY in agent body markdown and in message-schema's example paths. This keeps the template surface minimal. + +However, `rules/01-session.md` and `rules/04-tools.md` present a problem: their content differs between Claude and Codex (`.claude/memory/` vs `memory/`, `/clear` vs "new session"). Two approaches: + +**Approach A -- Template the rules too:** Add `${VAR}` placeholders to rules and expand them per-target. This means rules can't be symlinked; they must be copied into each output directory. +**Approach B -- Make rules fully generic:** Use tool-agnostic language ("the project memory directory", "suggest clearing context"). No placeholders needed; rules stay shared. + +**Decision: Approach B.** The rules are guidance for agent behavior, not config. Generic language ("the project memory directory at the project root") communicates the intent without coupling to a specific path. The exact path is already established by the agent body templates and skills. This keeps the template surface to just agent bodies + message-schema examples. + +**Revised skill/rule edit strategy:** +- `rules/01-session.md`: Replace `.claude/memory/` with `memory/` (tool-agnostic path). This works for both targets because the rules describe conceptual behavior ("persist in the memory directory"), and the actual path resolution happens in agent instructions. +- `rules/04-tools.md`: Replace `suggest /clear` with `suggest clearing context or starting a new session` (tool-agnostic). +- `skills/worker-protocol/SKILL.md`: Replace `use Read/Glob/Grep directly` with `verify by reading the relevant files directly` (tool-agnostic). +- `skills/qa-checklist/SKILL.md`: Replace `Verify with Read/Grep if uncertain` with `Verify by reading the code if uncertain` (tool-agnostic). +- `skills/project/SKILL.md`: This one references a concrete file path (`.claude/skills/project.md`). Two options: (a) make it generic ("check for a project-specific skill file in the standard location"), or (b) template it. Since the path genuinely differs between tools and is specific enough to matter, **template it** with `${PROJECT_SKILL_PATH}`. This means the project skill gets expanded per-target, not symlinked. But since skills are directory-symlinked as a whole, we need a different approach: generate only this one skill per-target, or restructure. + +**Revised approach for project skill:** The simplest solution is to make the path generic. The project skill says "check for a project-specific skill file" -- the agent already knows where to look because each tool has its own conventions. Change the instruction to: "Check for a project-specific skill file in the current working directory's configuration. For Claude Code, this is `.claude/skills/project.md`. For Codex, this is discovered via the standard skill path." Actually this leaks tool awareness into the shared file. + +**Final decision for project skill:** Make it fully generic: "Before starting any work, check for a project-specific skill file in the current working directory. The location depends on the tool configuration." The concrete path is not needed -- each tool resolves its own skill paths. The skill's purpose is behavioral ("check for project context before starting"), not path-specific. + +Similarly for message-schema `plan_file` examples: these show `.claude/plans/kebab-case-title.md` as an example value. For Codex, this would be `plans/kebab-case-title.md`. Since message-schema is loaded as a skill (shared), we should either template it or make the example generic. **Decision:** Use `plans/kebab-case-title.md` as the example (dropping the `.claude/` prefix). This is the tool-agnostic path. Claude's architect agent body already specifies the full `.claude/plans/` path, so the schema example doesn't need to repeat the tool-specific prefix. + +**Final template variable list (reduced):** + +Only agent body markdown files use `${VAR}` placeholders. Everything else is made tool-agnostic by direct edit. + +| Variable | Claude value | Codex value | Used in | +|---|---|---|---| +| `${PLANS_DIR}` | `.claude/plans` | `plans` | architect.md body | +| `${WEB_SEARCH}` | `via WebFetch/WebSearch` | `via web search` | reviewer.md body | +| `${SEARCH_TOOLS}` | `Use Grep/Glob/Read` | `Search the codebase` | debugger.md body, documenter.md body | + +Skills, rules, and message-schema are made tool-agnostic by direct edit (no placeholders). + +## Approach + +**Strategy:** Two-layer approach. + +1. **Layer 1 -- Tool-agnostic shared content.** Skills and rules are edited to remove Claude-specific tool names and paths. They become shared infrastructure, symlinked from both output directories. + +2. **Layer 2 -- Templated agent bodies.** Agent markdown files in `agents/` gain `${VAR}` placeholders in their body text (not frontmatter). `generate.sh` expands these with tool-specific values and writes the results to `claude/` and `codex/`. + +The generator (`generate.sh`) replaces `generate-codex.sh` and handles both targets: +- **Claude target:** Expand templates with Claude values, copy frontmatter-intact agent .md files to `claude/agents/`, copy CLAUDE.md and settings.json, symlink to shared skills/rules. +- **Codex target:** Expand templates with Codex values, convert YAML frontmatter to TOML (preserving existing model/effort/sandbox mapping logic), generate AGENTS.md from CLAUDE.md + expanded rules, generate config.toml from settings.json. + +**Alternative considered: Jinja/m4 templating.** Rejected -- envsubst is simpler, already available via gettext in Nix, and sufficient for the ~3 variable substitutions in agent bodies. The complexity of Jinja (conditional blocks, filters) is not needed. + +**Alternative considered: Keep Claude agents as source, derive Codex only.** This is the current approach. Rejected because it means the "source" files contain Claude-specific references that leak into Codex output (the current bug this refactor fixes). Making the source tool-agnostic eliminates the class of bugs where a Codex agent says "Use Grep" or references `.claude/plans/`. + +## Risks & Gotchas + +1. **envsubst touching unintended `$` in agent bodies.** Mitigated by using the scoped form: `envsubst '${PLANS_DIR} ${WEB_SEARCH} ${SEARCH_TOOLS}'`. Only listed variables are substituted. The architect body contains `$` in example YAML blocks, which must NOT be substituted. + +2. **YAML frontmatter containing `$`.** The frontmatter is not passed through envsubst -- only the body. The generator extracts frontmatter and body separately, expands only the body, then reassembles. + +3. **Skills/rules shared as symlinks -- edit affects both targets immediately.** This is intentional. The skills and rules are tool-agnostic after Wave 1, so sharing is correct. But if someone adds a Claude-specific reference to a shared skill later, it leaks to Codex. The README should document this constraint. + +4. **Codex config.toml gets overwritten.** The user's `codex/config.toml` has been manually edited (different content than what generate-codex.sh produces). `generate.sh` will overwrite it. Mitigation: document that `codex/config.toml` is generated and should not be hand-edited. User customizations should go in the source `settings.json`. + +5. **install.sh behavior change.** Currently installs directly from `agents/` source. After this change, it installs from `claude/agents/` (generated). Users must run `generate.sh` before `install.sh`. The install script should check for this and error with guidance. + +6. **orchestrate skill references `.claude/plans/` paths.** This is acceptable -- orchestrate is Claude-only (not used by Codex). The skill is still shared via the skills directory symlink, but Codex agents don't load it (it's not in their skills list). + +## Risk Tags + +breaking-change (install.sh workflow changes: generate.sh must run first), data-mutation (generates files to claude/ and codex/ directories) + +## Implementation Waves + +### Wave 1 -- Make skills and rules tool-agnostic (4 parallel tasks) + +These are independent edits to different files. No task depends on another. + +- [ ] **Step 1: Template agent bodies** -- Add `${VAR}` placeholders to agent markdown files. + - `agents/architect.md`: Replace `.claude/plans/.md` with `${PLANS_DIR}/.md` (line 19) and `.claude/plans/kebab-case-title.md` with `${PLANS_DIR}/kebab-case-title.md` (line 106). There is also a `.claude/plans/` reference on line 69 inside the orchestrator's resume instruction -- replace that too. Verify no other `.claude/` references exist in the body. + - `agents/reviewer.md`: Replace `via WebFetch/WebSearch` with `${WEB_SEARCH}` (line 33). + - `agents/debugger.md`: Replace `Use Grep to find the relevant code` with `${SEARCH_TOOLS} to find the relevant code` (line 25). Verify the surrounding sentence reads naturally. + - `agents/documenter.md`: Replace `Use Read/Glob/Grep to understand the actual behavior` with `${SEARCH_TOOLS} to understand the actual behavior` (line 29). + +- [ ] **Step 2: Make message-schema tool-agnostic** -- Edit `skills/message-schema/SKILL.md`. + - Replace `plan_file: .claude/plans/kebab-case-title.md` with `plan_file: plans/kebab-case-title.md` (lines 155, 205). This is an example value in the schema, not a literal config -- using the generic path is correct. + - Do NOT change the envelope structure or field names. + +- [ ] **Step 3: Make skills tool-agnostic** -- Edit skills that contain Claude-specific tool names. + - `skills/worker-protocol/SKILL.md` line 50: Replace `use Read/Glob/Grep directly. Don't guess at file contents — verify.` with `verify by reading the relevant files. Don't guess at file contents.` + - `skills/qa-checklist/SKILL.md` line 13: Replace `Verify with Read/Grep if uncertain.` with `Verify by reading the code if uncertain.` + - `skills/project/SKILL.md` lines 7, 9: Replace `.claude/skills/project.md` with `a project-specific skill file`. Rewrite the two sentences: + - Line 7: "Before starting any work, check for a project-specific skill file in the current working directory's tool configuration." + - Line 9: "If one exists, read it and treat its contents as additional instructions..." + - Do NOT edit `skills/orchestrate/SKILL.md` (stays Claude-only) or `skills/conventions/SKILL.md` (already clean). + +- [ ] **Step 4: Make rules tool-agnostic** -- Edit rules with Claude-specific references. + - `rules/01-session.md`: Replace all three occurrences of `.claude/memory/` with `memory/`. Update surrounding prose if needed for clarity. The CLAUDE.md hierarchy reference on line 3 is fine -- it's a generic concept name, not a file path (each tool has its own hierarchy). + - `rules/04-tools.md` line 17: Replace `suggest /clear` with `suggest clearing context or starting a new session`. + +### Wave 2 -- Infrastructure (depends on Wave 1 for knowing the final variable list) + +- [ ] **Step 5: Update .gitignore and flake.nix** -- Support infrastructure for the new generator. + - `.gitignore`: Add `claude/` line (generated output, same treatment as `codex/`). Keep the existing `codex/` line. + - `flake.nix`: Add `pkgs.gettext` to the devShell packages list (provides `envsubst`). Keep existing `pkgs.yq-go` and `pkgs.codex`. + +### Wave 3 -- Generator and installer (depends on Wave 1 for templates, Wave 2 for infrastructure) + +- [ ] **Step 6: Write generate.sh** -- New unified generator replacing `generate-codex.sh`. + - Location: `generate.sh` (project root, same level as old `generate-codex.sh`) + - Delete `generate-codex.sh` (or rename -- but deleting is cleaner since the new script fully replaces it) + - **Claude target generation:** + 1. Create `claude/agents/` directory + 2. For each `agents/*.md`: extract frontmatter and body separately. Run body through `envsubst '${PLANS_DIR} ${WEB_SEARCH} ${SEARCH_TOOLS}'` with Claude variable values. Reassemble frontmatter + expanded body. Write to `claude/agents/.md`. + 3. Copy `CLAUDE.md` to `claude/CLAUDE.md` + 4. Copy `settings.json` to `claude/settings.json` + 5. Create relative symlinks: `claude/rules -> ../rules`, `claude/skills -> ../skills` + - **Codex target generation** (preserve all existing logic from `generate-codex.sh`): + 1. Create `codex/agents/` directory + 2. For each `agents/*.md`: extract frontmatter and body. Run body through `envsubst '${PLANS_DIR} ${WEB_SEARCH} ${SEARCH_TOOLS}'` with Codex variable values. Convert frontmatter to TOML format (same mapping functions: `map_model`, `map_effort`, `map_sandbox_mode`). Write to `codex/agents/.toml`. + 3. Generate `codex/AGENTS.md` from `CLAUDE.md` + rules/*.md (same logic as current `generate-codex.sh`) + 4. Generate `codex/config.toml` from `settings.json` (same logic as current) + - **Script structure:** + ```bash + #!/usr/bin/env bash + set -euo pipefail + + SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + + # --- Variable definitions --- + # Claude target + declare -A CLAUDE_VARS=( + [PLANS_DIR]=".claude/plans" + [WEB_SEARCH]="via WebFetch/WebSearch" + [SEARCH_TOOLS]="Use Grep/Glob/Read" + ) + # Codex target + declare -A CODEX_VARS=( + [PLANS_DIR]="plans" + [WEB_SEARCH]="via web search" + [SEARCH_TOOLS]="Search the codebase" + ) + + # --- Shared functions --- + extract_frontmatter() { ... } # yq --front-matter=extract + extract_body() { ... } # awk pattern from current script + expand_body() { ... } # envsubst with scoped variable list + + # --- Claude generation --- + generate_claude() { ... } + + # --- Codex generation (ported from generate-codex.sh) --- + generate_codex() { ... } + + generate_claude + generate_codex + echo "Done. Run ./install.sh to link into tool directories." + ``` + - **Key detail for envsubst scoping:** The `expand_body` function must export only the target's variables, run envsubst with the explicit variable list, then unset them. This prevents cross-contamination and protects other `$` references in the body. + ```bash + expand_body() { + local body="$1" + shift + # Remaining args are KEY=VALUE pairs + local var_list="" + for pair in "$@"; do + local key="${pair%%=*}" + local val="${pair#*=}" + export "$key=$val" + var_list+=" \${$key}" + done + echo "$body" | envsubst "$var_list" + for pair in "$@"; do + unset "${pair%%=*}" + done + } + ``` + +- [ ] **Step 7: Update install.sh** -- Rewire to use generated output. + - **Claude installation:** Change `AGENTS_SRC` from `$SCRIPT_DIR/agents` to `$SCRIPT_DIR/claude/agents`. Change `CLAUDE_MD_SRC` from `$SCRIPT_DIR/CLAUDE.md` to `$SCRIPT_DIR/claude/CLAUDE.md`. Change `SETTINGS_SRC` from `$SCRIPT_DIR/settings.json` to `$SCRIPT_DIR/claude/settings.json`. Skills and rules still symlink from source (they're tool-agnostic and shared): `SKILLS_SRC="$SCRIPT_DIR/skills"`, `RULES_SRC="$SCRIPT_DIR/rules"` (unchanged). + - **Pre-flight check:** At the top of install.sh, verify `claude/` directory exists. If not, print: `"Error: claude/ not found. Run ./generate.sh first."` and exit 1. + - **Codex installation:** Change `codex/agents` source to `$SCRIPT_DIR/codex/agents` (already correct). Keep all other Codex paths the same. + - Preserve the entire symlink helper infrastructure (create_symlink, create_file_symlink, OS detection, backup logic). + +### Wave 4 -- Documentation and cleanup (depends on Wave 3) + +- [ ] **Step 8: Update README.md** -- Reflect the new workflow. + - Quick install section: add `./generate.sh` before `./install.sh` + - Codex CLI compatibility section: update to reflect `generate.sh` replaces `generate-codex.sh` and now generates both targets + - "What gets generated" table: add Claude row showing `agents/*.md` -> `claude/agents/*.md` + - Add a note about template variables and the tool-agnostic constraint on shared skills/rules + - Remove references to `generate-codex.sh` + +## Acceptance Criteria + +1. `./generate.sh` produces `claude/agents/*.md` with Claude-specific values expanded (`.claude/plans/`, `Use Grep/Glob/Read`, etc.) -- verified by: grep the generated files for expanded values, confirm no `${` remains +2. `./generate.sh` produces `codex/agents/*.toml` with Codex-specific values expanded (`plans/`, `Search the codebase`, etc.) -- verified by: grep the generated files for expanded values, confirm no `${` remains +3. No Claude-specific tool names (`Read`, `Glob`, `Grep`, `WebFetch`, `WebSearch`, `Edit`, `Write`) appear in skills (except orchestrate) or rules -- verified by: grep shared skills and rules for these tool names +4. No `.claude/` paths appear in skills (except orchestrate) or rules -- verified by: grep shared skills and rules for `.claude/` +5. `./install.sh` errors with a helpful message if `claude/` does not exist -- verified by: manual test +6. `./install.sh` successfully symlinks from `claude/` when it exists -- verified by: manual test +7. Codex output is functionally identical to what `generate-codex.sh` produced (same TOML structure, same model/effort/sandbox mappings) except with Codex-specific values substituted -- verified by: diff old and new codex/ output +8. The `envsubst` expansion does NOT touch `$` characters in YAML frontmatter or example code blocks -- verified by: inspect architect.md generated output for intact `$` in YAML examples +9. `flake.nix` devShell includes `gettext` (provides envsubst) -- verified by: `nix develop -c which envsubst` diff --git a/.gitignore b/.gitignore index 0da7350..4c34028 100644 --- a/.gitignore +++ b/.gitignore @@ -9,5 +9,6 @@ settings.local.json .DS_Store Thumbs.db -# Generated Codex CLI config (derived from Claude source files via generate-codex.sh) +# Generated output (derived from source templates via generate.sh) +claude/ codex/ diff --git a/README.md b/README.md index c8ab5c0..9647e8d 100644 --- a/README.md +++ b/README.md @@ -7,10 +7,12 @@ A portable Claude Code agent team configuration. Clone it, run `install.sh`, and ```bash git clone ~/agent-team cd ~/agent-team -./install.sh +nix develop # enter devShell with yq + envsubst +./generate.sh # generate Claude + Codex config from templates +./install.sh # symlinks into ~/.claude/ and ~/.codex/ (if present) ``` -The script symlinks `agents/`, `skills/`, `rules/`, `CLAUDE.md`, and `settings.json` into `~/.claude/`. Works on Linux, macOS, and Windows (Git Bash). +The scripts generate configuration for both Claude Code and Codex CLI (if `~/.codex/` exists), then symlink agents, skills, rules, CLAUDE.md, and settings.json into `~/.claude/`. Works on Linux, macOS, and Windows (Git Bash). ## Maintenance @@ -66,19 +68,20 @@ This project also generates configuration for [OpenAI Codex CLI](https://github. ### Setup ```bash -nix develop # enter devShell with yq -./generate-codex.sh # generate Codex config from Claude source files +nix develop # enter devShell with yq + envsubst +./generate.sh # generate Claude + Codex config from templates ./install.sh # installs both Claude and Codex (if ~/.codex exists) ``` ### What gets generated -| Source | Generated | Codex location | +| Source | Generated | Location | |---|---|---| -| `agents/*.md` | `codex/agents/*.toml` | `~/.codex/agents/` | +| `agents/*.md` (templates) | `claude/agents/*.md` | `~/.claude/agents/` | +| `agents/*.md` (templates) | `codex/agents/*.toml` | `~/.codex/agents/` | | `CLAUDE.md` + `rules/*.md` | `codex/AGENTS.md` | `~/.codex/AGENTS.md` | | `settings.json` | `codex/config.toml` | `~/.codex/config.toml` | -| `skills/` | (shared as-is) | `~/.agents/skills/` | +| `skills/` | (shared as-is) | `~/.claude/skills/` + `~/.agents/skills/` | ### Model mapping @@ -88,6 +91,18 @@ nix develop # enter devShell with yq | `sonnet` | `o4-mini` | | `haiku` | `o4-mini` | +### Template variables + +Agent body text uses `${VAR}` placeholders that are expanded per-target by `generate.sh`: + +| Variable | Claude | Codex | +|---|---|---| +| `${PLANS_DIR}` | `.claude/plans` | `plans` | +| `${WEB_SEARCH}` | `via WebFetch/WebSearch` | `via web search` | +| `${SEARCH_TOOLS}` | `Use Grep/Glob/Read` | `Search the codebase` | + +Skills and rules are tool-agnostic and shared as-is — do not add tool-specific references to them. + ## Project-specific config Each project repo can extend the team with local config in `.claude/`: diff --git a/agents/architect.md b/agents/architect.md index 6256132..0666921 100644 --- a/agents/architect.md +++ b/agents/architect.md @@ -16,7 +16,7 @@ You are an architect. You handle the full planning pipeline: triage, architectur Never implement anything. Never modify source files. Analyze, evaluate, plan. -**Plan persistence:** Always write the approved plan to `.claude/plans/.md`. Never return the plan inline without writing it first. Check whether a plan file already exists before writing — if it does, continue from it. +**Plan persistence:** Always write the approved plan to `${PLANS_DIR}/.md`. Never return the plan inline without writing it first. Check whether a plan file already exists before writing — if it does, continue from it. Frontmatter format: ``` @@ -103,7 +103,7 @@ After writing the plan file, return a `plan_result` envelope: --- type: plan_result signal: plan_complete | blocked -plan_file: .claude/plans/kebab-case-title.md +plan_file: ${PLANS_DIR}/kebab-case-title.md wave_count: 3 step_count: 7 risk_tags: diff --git a/agents/debugger.md b/agents/debugger.md index 33fb7bc..154cfce 100644 --- a/agents/debugger.md +++ b/agents/debugger.md @@ -21,7 +21,7 @@ You are a debugger. Your job is to find the root cause of a bug and apply the mi Confirm the bug is reproducible before doing anything else. Run the failing test, command, or request. If you cannot reproduce it, say so immediately — do not guess at a fix. ### 2. Isolate -Narrow down where the failure originates. Read the stack trace or error message carefully. Use Grep to find the relevant code. Read the actual code — do not assume you know what it does. +Narrow down where the failure originates. Read the stack trace or error message carefully. ${SEARCH_TOOLS} to find the relevant code. Read the actual code — do not assume you know what it does. ### 3. Hypothesize Form a specific hypothesis: "The bug is caused by X because Y." State it explicitly before writing any fix. If you have multiple hypotheses, rank them by likelihood. diff --git a/agents/documenter.md b/agents/documenter.md index b68a41e..a20b248 100644 --- a/agents/documenter.md +++ b/agents/documenter.md @@ -26,7 +26,7 @@ You are a documentation specialist. Your job is to read code and produce accurat ## How you operate -1. **Read the code first.** Never document what you haven't read. Use Read/Glob/Grep to understand the actual behavior before writing a word. +1. **Read the code first.** Never document what you haven't read. ${SEARCH_TOOLS} to understand the actual behavior before writing a word. 2. **Match existing conventions.** Check for existing docs in the repo — tone, structure, format — and match them. Check `skills/conventions` for project-specific rules. 3. **Be accurate, not aspirational.** Document what the code does, not what it should do. If behavior is unclear, say so — don't invent. 4. **Link, don't duplicate.** Where a concept is already documented elsewhere (official docs, another file), link to it rather than re-explaining. diff --git a/agents/reviewer.md b/agents/reviewer.md index 05d2b3a..6e737c0 100644 --- a/agents/reviewer.md +++ b/agents/reviewer.md @@ -30,7 +30,7 @@ You are a reviewer. You do two things in one pass: quality review and claim veri ## Claim verification - **Acceptance criteria** — walk each criterion explicitly by number. Clean code that doesn't do what was asked is a FAIL. -- **API and library usage** — verify against official docs via WebFetch/WebSearch when the implementation uses external APIs, libraries, or non-obvious patterns +- **API and library usage** — verify against official docs ${WEB_SEARCH} when the implementation uses external APIs, libraries, or non-obvious patterns - **File and path claims** — do they exist? - **Logic correctness** — does the implementation actually solve the problem? - **Contradictions** — between worker output and source code, between claims and evidence diff --git a/flake.nix b/flake.nix index c3c4b14..f038eb6 100644 --- a/flake.nix +++ b/flake.nix @@ -7,7 +7,7 @@ in { devShells = forAllSystems (pkgs: { default = pkgs.mkShell { - packages = [ pkgs.yq-go pkgs.codex ]; + packages = [ pkgs.yq-go pkgs.gettext pkgs.codex ]; }; }); }; diff --git a/generate-codex.sh b/generate-codex.sh deleted file mode 100755 index 8bf99a2..0000000 --- a/generate-codex.sh +++ /dev/null @@ -1,184 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -# generate-codex.sh — generates Codex CLI config from Claude source files. -# Claude source files are the source of truth; this script derives Codex equivalents. -# Idempotent: safe to run multiple times. - -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -CODEX_DIR="$SCRIPT_DIR/codex" -CODEX_AGENTS_DIR="$CODEX_DIR/agents" -AGENTS_SRC="$SCRIPT_DIR/agents" -RULES_DIR="$SCRIPT_DIR/rules" -CLAUDE_MD="$SCRIPT_DIR/CLAUDE.md" -SETTINGS_JSON="$SCRIPT_DIR/settings.json" - -# Create output directories -mkdir -p "$CODEX_DIR" -mkdir -p "$CODEX_AGENTS_DIR" - -# Clean existing generated agent TOMLs -rm -f "$CODEX_AGENTS_DIR"/*.toml - -# --------------------------------------------------------------------------- -# map_model — maps Claude model name to Codex model name -# --------------------------------------------------------------------------- -map_model() { - local model="$1" - case "$model" in - opus) echo "o3" ;; - sonnet) echo "o4-mini" ;; - haiku) echo "o4-mini" ;; - *) echo "o4-mini" ;; - esac -} - -# --------------------------------------------------------------------------- -# map_effort — maps Claude effort level to Codex model_reasoning_effort -# --------------------------------------------------------------------------- -map_effort() { - local effort="$1" - case "$effort" in - low) echo "low" ;; - medium) echo "medium" ;; - high) echo "high" ;; - max) echo "xhigh" ;; - *) echo "medium" ;; - esac -} - -# --------------------------------------------------------------------------- -# map_sandbox_mode — determines Codex sandbox_mode from agent frontmatter -# $1 = permissionMode value (plan / acceptEdits / "") -# $2 = tools list (comma-separated) -# --------------------------------------------------------------------------- -map_sandbox_mode() { - local permission_mode="$1" - local tools="$2" - - # plan mode is read-only - if [ "$permission_mode" = "plan" ]; then - echo "read-only" - return - fi - - # acceptEdits with Write or Edit tool → workspace-write - if [ "$permission_mode" = "acceptEdits" ]; then - if echo "$tools" | grep -qE '\b(Write|Edit)\b'; then - echo "workspace-write" - return - fi - fi - - # Default: read-only - echo "read-only" -} - -# --------------------------------------------------------------------------- -# generate_agent_toml — converts a single agent .md file to Codex .toml -# --------------------------------------------------------------------------- -generate_agent_toml() { - local src_file="$1" - local agent_basename - agent_basename="$(basename "$src_file" .md)" - local dst_file="$CODEX_AGENTS_DIR/${agent_basename}.toml" - - # Extract YAML frontmatter using yq - local frontmatter - frontmatter="$(yq --front-matter=extract '.' "$src_file")" - - # Extract individual fields from frontmatter - local name description model effort permission_mode tools disallowed_tools - name="$(echo "$frontmatter" | yq '.name // ""')" - description="$(echo "$frontmatter" | yq '.description // ""')" - model="$(echo "$frontmatter" | yq '.model // ""')" - effort="$(echo "$frontmatter" | yq '.effort // ""')" - permission_mode="$(echo "$frontmatter" | yq '.permissionMode // ""')" - tools="$(echo "$frontmatter" | yq '.tools // ""')" - disallowed_tools="$(echo "$frontmatter" | yq '.disallowedTools // ""')" - - # Map to Codex equivalents - local codex_model codex_effort codex_sandbox - codex_model="$(map_model "$model")" - codex_effort="$(map_effort "${effort:-medium}")" - codex_sandbox="$(map_sandbox_mode "$permission_mode" "$tools")" - - # Extract markdown body (everything after the closing frontmatter ---) - # The frontmatter block starts at line 1 with --- and ends at the second --- - local body - body="$(awk 'BEGIN{fm=0} /^---$/{if(fm==0){fm=1;next} if(fm==1){fm=2;next}} fm==2{print}' "$src_file")" - - # Build developer_instructions: append disallowedTools note if present - local developer_instructions - developer_instructions="$body" - if [ -n "$disallowed_tools" ] && [ "$disallowed_tools" != "null" ]; then - developer_instructions="${developer_instructions} - -You do NOT have access to these tools: ${disallowed_tools}" - fi - - # Write TOML output - cat > "$dst_file" < "$CODEX_DIR/AGENTS.md" -echo "Generated: $CODEX_DIR/AGENTS.md" - -# --------------------------------------------------------------------------- -# Generate config.toml — derive sandbox_mode from settings.json defaultMode -# --------------------------------------------------------------------------- -echo "" -echo "Generating codex/config.toml..." - -default_mode="$(yq -r '.permissions.defaultMode // "acceptEdits"' "$SETTINGS_JSON")" - -# Map Claude defaultMode to Codex sandbox_mode -case "$default_mode" in - plan) config_sandbox="read-only" ;; - acceptEdits) config_sandbox="workspace-write" ;; - *) config_sandbox="workspace-write" ;; -esac - -cat > "$CODEX_DIR/config.toml" < ../rules" + + ln -s ../skills "$CLAUDE_DIR/skills" + echo "Symlinked: $CLAUDE_DIR/skills -> ../skills" + + # Generate agent .md files with expanded template variables + for agent_file in "$AGENTS_SRC"/*.md; do + [ -f "$agent_file" ] || continue + + local agent_basename + agent_basename="$(basename "$agent_file")" + local dst_file="$CLAUDE_AGENTS_DIR/$agent_basename" + + # Extract frontmatter and body separately + local frontmatter body expanded_body + frontmatter="$(extract_frontmatter_block "$agent_file")" + body="$(extract_body "$agent_file")" + expanded_body="$(expand_body "$body" "${CLAUDE_VARS[@]}")" + + # Reassemble: frontmatter + expanded body + { + echo "$frontmatter" + echo "$expanded_body" + } > "$dst_file" + + echo "Generated: $dst_file" + done +} + +# --------------------------------------------------------------------------- +# generate_codex — produces codex/ output directory +# --------------------------------------------------------------------------- +generate_codex() { + echo "" + echo "=== Generating Codex output ===" + + # Clean and recreate output directories + rm -rf "$CODEX_DIR" + mkdir -p "$CODEX_AGENTS_DIR" + + # Generate agent .toml files + echo "Generating Codex agent definitions..." + for agent_file in "$AGENTS_SRC"/*.md; do + [ -f "$agent_file" ] || continue + + local agent_basename + agent_basename="$(basename "$agent_file" .md)" + local dst_file="$CODEX_AGENTS_DIR/${agent_basename}.toml" + + # Extract YAML frontmatter using yq + local frontmatter + frontmatter="$(yq --front-matter=extract '.' "$agent_file")" + + # Extract individual fields from frontmatter + local name description model effort permission_mode tools disallowed_tools + name="$(echo "$frontmatter" | yq '.name // ""')" + description="$(echo "$frontmatter" | yq '.description // ""')" + model="$(echo "$frontmatter" | yq '.model // ""')" + effort="$(echo "$frontmatter" | yq '.effort // ""')" + permission_mode="$(echo "$frontmatter" | yq '.permissionMode // ""')" + tools="$(echo "$frontmatter" | yq '.tools // ""')" + disallowed_tools="$(echo "$frontmatter" | yq '.disallowedTools // ""')" + + # Map to Codex equivalents + local codex_model codex_effort codex_sandbox + codex_model="$(map_model "$model")" + codex_effort="$(map_effort "${effort:-medium}")" + codex_sandbox="$(map_sandbox_mode "$permission_mode" "$tools")" + + # Extract and expand body with Codex variable values + local body expanded_body + body="$(extract_body "$agent_file")" + expanded_body="$(expand_body "$body" "${CODEX_VARS[@]}")" + + # Build developer_instructions: append disallowedTools note if present + local developer_instructions + developer_instructions="$expanded_body" + if [ -n "$disallowed_tools" ] && [ "$disallowed_tools" != "null" ]; then + developer_instructions="${developer_instructions} + +You do NOT have access to these tools: ${disallowed_tools}" + fi + + # Write TOML output + cat > "$dst_file" < "$CODEX_DIR/AGENTS.md" + echo "Generated: $CODEX_DIR/AGENTS.md" + + # Generate config.toml — derive sandbox_mode from settings.json defaultMode + echo "" + echo "Generating codex/config.toml..." + + local default_mode + default_mode="$(yq -r '.permissions.defaultMode // "acceptEdits"' "$SETTINGS_JSON")" + + # Map Claude defaultMode to Codex sandbox_mode + local config_sandbox + case "$default_mode" in + plan) config_sandbox="read-only" ;; + acceptEdits) config_sandbox="workspace-write" ;; + *) config_sandbox="workspace-write" ;; + esac + + cat > "$CODEX_DIR/config.toml" < Date: Thu, 2 Apr 2026 08:55:38 -0400 Subject: [PATCH 24/36] fix: flake.nix typo, generate.sh word-splitting, README accuracy MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - flake.nix: fix gettex → gettext typo, restore codex package - generate.sh: replace ls-based loop with direct glob (word-splitting safe) - README.md: fix AGENTS.md source column (rules/*.md only, not CLAUDE.md) --- README.md | 2 +- flake.nix | 6 +++++- generate.sh | 2 +- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 9647e8d..7622784 100644 --- a/README.md +++ b/README.md @@ -79,7 +79,7 @@ nix develop # enter devShell with yq + envsubst |---|---|---| | `agents/*.md` (templates) | `claude/agents/*.md` | `~/.claude/agents/` | | `agents/*.md` (templates) | `codex/agents/*.toml` | `~/.codex/agents/` | -| `CLAUDE.md` + `rules/*.md` | `codex/AGENTS.md` | `~/.codex/AGENTS.md` | +| `rules/*.md` | `codex/AGENTS.md` | `~/.codex/AGENTS.md` | | `settings.json` | `codex/config.toml` | `~/.codex/config.toml` | | `skills/` | (shared as-is) | `~/.claude/skills/` + `~/.agents/skills/` | diff --git a/flake.nix b/flake.nix index f038eb6..3d58609 100644 --- a/flake.nix +++ b/flake.nix @@ -7,7 +7,11 @@ in { devShells = forAllSystems (pkgs: { default = pkgs.mkShell { - packages = [ pkgs.yq-go pkgs.gettext pkgs.codex ]; + packages = with pkgs; [ + yq-go + gettext + codex + ]; }; }); }; diff --git a/generate.sh b/generate.sh index d6e4601..cfb08fa 100755 --- a/generate.sh +++ b/generate.sh @@ -254,7 +254,7 @@ TOML echo "# Agent Team Instructions" echo "" echo "Agent-team specific protocols live in skills (orchestrate, conventions, worker-protocol, qa-checklist, message-schema, project)." - for rules_file in $(ls "$RULES_DIR"/*.md | sort); do + for rules_file in "$RULES_DIR"/*.md; do echo "" cat "$rules_file" done From 49b2f71c94a7742f0ff0b868100a556a9f690f60 Mon Sep 17 00:00:00 2001 From: Bryan Ramos Date: Thu, 2 Apr 2026 08:58:49 -0400 Subject: [PATCH 25/36] fix(orchestrate): replace .claude/plans/ with generic plans/ path for cross-tool compatibility --- skills/orchestrate/SKILL.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/skills/orchestrate/SKILL.md b/skills/orchestrate/SKILL.md index ac88039..9176940 100644 --- a/skills/orchestrate/SKILL.md +++ b/skills/orchestrate/SKILL.md @@ -63,9 +63,9 @@ Each researcher receives: the specific question, why it's needed, where to look, Collect all outputs. Assemble into a single `## Research Context` block. **Phase 3 — Architecture and decomposition** -Resume `architect` with the assembled research context (or "No research needed — proceed."). It produces the full plan: interface contracts, wave assignments, acceptance criteria — written to `.claude/plans/.md`. +Resume `architect` with the assembled research context (or "No research needed — proceed."). It produces the full plan: interface contracts, wave assignments, acceptance criteria — written to `plans/<title>.md`. -**Resuming from an existing plan:** If a `.claude/plans/` file exists for this task, pass its path to the architect instead of running the pipeline again. +**Resuming from an existing plan:** If a `plans/` file exists for this task, pass its path to the architect instead of running the pipeline again. ### Step 4 — Consume the plan @@ -213,7 +213,7 @@ Workers return `signal: rfr` when done. You control commits: - Merge worktree branches after individual validation - On Tier 2+: merge each worker's branch after validation, resolve conflicts if branches overlap -Only the orchestrator updates the plan file. Workers must not modify `.claude/plans/`. +Only the orchestrator updates the plan file. Workers must not modify `plans/`. ### Message schema From 68217dfd4b6dc5b3f3fd1bb395991ff6364754be Mon Sep 17 00:00:00 2001 From: Bryan Ramos <bryan@ramos.codes> Date: Thu, 2 Apr 2026 09:02:35 -0400 Subject: [PATCH 26/36] fix: correct Codex model mappings and effort levels MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - opus → gpt-5.4, sonnet → gpt-5.3-codex, haiku → gpt-5.1-codex-mini - effort max → extra high (was xhigh) - config.toml default model → gpt-5.3-codex - README model mapping table updated --- README.md | 6 +++--- generate.sh | 12 ++++++------ 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 7622784..152c2ed 100644 --- a/README.md +++ b/README.md @@ -87,9 +87,9 @@ nix develop # enter devShell with yq + envsubst | Claude Code | Codex CLI | |---|---| -| `opus` | `o3` | -| `sonnet` | `o4-mini` | -| `haiku` | `o4-mini` | +| `opus` | `gpt-5.4` | +| `sonnet` | `gpt-5.3-codex` | +| `haiku` | `gpt-5.1-codex-mini` | ### Template variables diff --git a/generate.sh b/generate.sh index cfb08fa..5710c2e 100755 --- a/generate.sh +++ b/generate.sh @@ -81,10 +81,10 @@ expand_body() { map_model() { local model="$1" case "$model" in - opus) echo "o3" ;; - sonnet) echo "o4-mini" ;; - haiku) echo "o4-mini" ;; - *) echo "o4-mini" ;; + opus) echo "gpt-5.4" ;; + sonnet) echo "gpt-5.3-codex" ;; + haiku) echo "gpt-5.1-codex-mini" ;; + *) echo "gpt-5.3-codex" ;; esac } @@ -97,7 +97,7 @@ map_effort() { low) echo "low" ;; medium) echo "medium" ;; high) echo "high" ;; - max) echo "xhigh" ;; + max) echo "extra high" ;; *) echo "medium" ;; esac } @@ -277,7 +277,7 @@ TOML esac cat > "$CODEX_DIR/config.toml" <<TOML -model = "o4-mini" +model = "gpt-5.3-codex" model_reasoning_effort = "medium" sandbox_mode = "${config_sandbox}" approval_policy = "on-request" From 758ebd23bbb10163f25fb9bccfa5773f6c92e116 Mon Sep 17 00:00:00 2001 From: Bryan Ramos <bryan@ramos.codes> Date: Thu, 2 Apr 2026 09:19:17 -0400 Subject: [PATCH 27/36] updated --- .envrc | 1 + agents/architect.md | 1 - agents/auditor.md | 1 - agents/debugger.md | 1 - agents/documenter.md | 1 - agents/reviewer.md | 1 - agents/worker.md | 1 - generate.sh | 2 +- install.sh | 13 ++++++------- skills/project/SKILL.md | 11 ----------- 10 files changed, 8 insertions(+), 25 deletions(-) create mode 100644 .envrc delete mode 100644 skills/project/SKILL.md diff --git a/.envrc b/.envrc new file mode 100644 index 0000000..3550a30 --- /dev/null +++ b/.envrc @@ -0,0 +1 @@ +use flake diff --git a/agents/architect.md b/agents/architect.md index 0666921..c180268 100644 --- a/agents/architect.md +++ b/agents/architect.md @@ -9,7 +9,6 @@ maxTurns: 35 skills: - conventions - message-schema - - project --- You are an architect. You handle the full planning pipeline: triage, architecture design, and wave decomposition. Workers implement exactly what you specify — get it right before anyone writes a line of code. diff --git a/agents/auditor.md b/agents/auditor.md index fd9e414..3a26d67 100644 --- a/agents/auditor.md +++ b/agents/auditor.md @@ -10,7 +10,6 @@ skills: - conventions - message-schema - qa-checklist - - project --- You are an auditor. You do two things: security analysis and runtime validation. Never write, edit, or fix code — only identify, validate, and report. diff --git a/agents/debugger.md b/agents/debugger.md index 154cfce..fff6eec 100644 --- a/agents/debugger.md +++ b/agents/debugger.md @@ -10,7 +10,6 @@ skills: - worker-protocol - message-schema - qa-checklist - - project --- You are a debugger. Your job is to find the root cause of a bug and apply the minimal fix. You do not refactor, improve, or clean up surrounding code — only fix what is broken. diff --git a/agents/documenter.md b/agents/documenter.md index a20b248..adaa706 100644 --- a/agents/documenter.md +++ b/agents/documenter.md @@ -11,7 +11,6 @@ skills: - worker-protocol - message-schema - qa-checklist - - project --- You are a documentation specialist. Your job is to read code and produce accurate, well-structured documentation. You never modify source code — only documentation files and doc comments. diff --git a/agents/reviewer.md b/agents/reviewer.md index 6e737c0..41e2478 100644 --- a/agents/reviewer.md +++ b/agents/reviewer.md @@ -10,7 +10,6 @@ skills: - conventions - message-schema - qa-checklist - - project --- You are a reviewer. You do two things in one pass: quality review and claim verification. Never write, edit, or fix code — only flag and explain. diff --git a/agents/worker.md b/agents/worker.md index 1401d68..696b851 100644 --- a/agents/worker.md +++ b/agents/worker.md @@ -11,7 +11,6 @@ skills: - worker-protocol - message-schema - qa-checklist - - project --- You are a worker agent. You implement what you are assigned. Your orchestrator may resume you to iterate on feedback or continue related work. diff --git a/generate.sh b/generate.sh index 5710c2e..60e28e3 100755 --- a/generate.sh +++ b/generate.sh @@ -97,7 +97,7 @@ map_effort() { low) echo "low" ;; medium) echo "medium" ;; high) echo "high" ;; - max) echo "extra high" ;; + max) echo "xhigh" ;; *) echo "medium" ;; esac } diff --git a/install.sh b/install.sh index cb68372..0374fe8 100755 --- a/install.sh +++ b/install.sh @@ -130,7 +130,6 @@ create_file_symlink "$SETTINGS_SRC" "$SETTINGS_DST" "settings.json" # Codex CLI integration (optional — only if ~/.codex exists) CODEX_DIR="$HOME/.codex" -CODEX_AGENTS_DIR="$HOME/.agents" if [ -d "$CODEX_DIR" ]; then echo "" @@ -141,9 +140,12 @@ if [ -d "$CODEX_DIR" ]; then echo "Warning: codex/ not found. Run ./generate.sh first to generate Codex output." fi - # Skills shared via ~/.agents/skills/ (Codex discovery path) - mkdir -p "$CODEX_AGENTS_DIR" - create_symlink "$SKILLS_SRC" "$CODEX_AGENTS_DIR/skills" "codex skills" + # Skills: symlink each skill directory into ~/.codex/skills/ + # (Can't replace the whole directory — .system/ must remain intact) + for skill_dir in "$SKILLS_SRC"/*/; do + skill_name="$(basename "$skill_dir")" + create_symlink "$skill_dir" "$CODEX_DIR/skills/$skill_name" "codex skill: $skill_name" + done # Generated agents if [ -d "$SCRIPT_DIR/codex/agents" ]; then @@ -162,6 +164,3 @@ if [ -d "$CODEX_DIR" ]; then create_file_symlink "$SCRIPT_DIR/codex/config.toml" "$CODEX_DIR/config.toml" "codex config.toml" fi fi - -echo "" -echo "Done. Open Claude Code and load the orchestrate skill to begin." diff --git a/skills/project/SKILL.md b/skills/project/SKILL.md deleted file mode 100644 index c5a9d74..0000000 --- a/skills/project/SKILL.md +++ /dev/null @@ -1,11 +0,0 @@ ---- -name: project -description: Instructs agents to check for and ingest a project-specific skill file before starting work. -when_to_use: Loaded by all agents. Checks for .claude/skills/project.md in the working directory and ingests project-local conventions. ---- - -Before starting any work, check for a project-specific skill file in the current working directory's configuration. - -If it exists, read it and treat its contents as additional instructions — project conventions, architecture notes, domain context, or anything else the project maintainer has defined. These instructions take precedence over general defaults where they conflict. - -If it does not exist, continue without it. From 0667c42755667bf3d4c607e4000ccbd4c353dd69 Mon Sep 17 00:00:00 2001 From: Bryan Ramos <bryan@ramos.codes> Date: Thu, 2 Apr 2026 09:28:01 -0400 Subject: [PATCH 28/36] fixed bootstrap --- install.sh | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/install.sh b/install.sh index 0374fe8..dc63bf9 100755 --- a/install.sh +++ b/install.sh @@ -128,20 +128,17 @@ create_symlink "$RULES_SRC" "$RULES_DST" "rules" create_file_symlink "$CLAUDE_MD_SRC" "$CLAUDE_MD_DST" "CLAUDE.md" create_file_symlink "$SETTINGS_SRC" "$SETTINGS_DST" "settings.json" -# Codex CLI integration (optional — only if ~/.codex exists) +# Codex CLI integration (optional — only if codex/ output exists) CODEX_DIR="$HOME/.codex" -if [ -d "$CODEX_DIR" ]; then +if [ -d "$SCRIPT_DIR/codex" ]; then echo "" - echo "Codex CLI detected at $CODEX_DIR" - - # Warn if generated codex/ output is missing - if [ ! -d "$SCRIPT_DIR/codex" ]; then - echo "Warning: codex/ not found. Run ./generate.sh first to generate Codex output." - fi + echo "Codex output found — installing to $CODEX_DIR" + mkdir -p "$CODEX_DIR" # Skills: symlink each skill directory into ~/.codex/skills/ # (Can't replace the whole directory — .system/ must remain intact) + mkdir -p "$CODEX_DIR/skills" for skill_dir in "$SKILLS_SRC"/*/; do skill_name="$(basename "$skill_dir")" create_symlink "$skill_dir" "$CODEX_DIR/skills/$skill_name" "codex skill: $skill_name" From 7f0892d67bb05343302a0a7108e17f170ab6941f Mon Sep 17 00:00:00 2001 From: Bryan Ramos <bryan@ramos.codes> Date: Thu, 2 Apr 2026 09:35:19 -0400 Subject: [PATCH 29/36] updated --- .claude/memory/MEMORY.md | 1 - .claude/memory/inter_agent_schema.md | 13 - .../template-based-dual-target-generator.md | 338 ------------------ .gitignore | 2 + README.md | 30 +- install.sh | 2 +- 6 files changed, 19 insertions(+), 367 deletions(-) delete mode 100644 .claude/memory/MEMORY.md delete mode 100644 .claude/memory/inter_agent_schema.md delete mode 100644 .claude/plans/template-based-dual-target-generator.md diff --git a/.claude/memory/MEMORY.md b/.claude/memory/MEMORY.md deleted file mode 100644 index e910cac..0000000 --- a/.claude/memory/MEMORY.md +++ /dev/null @@ -1 +0,0 @@ -- [Inter-agent communication schema](inter_agent_schema.md) — YAML frontmatter envelopes implemented via message-schema skill diff --git a/.claude/memory/inter_agent_schema.md b/.claude/memory/inter_agent_schema.md deleted file mode 100644 index 4e40704..0000000 --- a/.claude/memory/inter_agent_schema.md +++ /dev/null @@ -1,13 +0,0 @@ ---- -name: Inter-agent communication schema — implemented -description: Typed YAML frontmatter envelopes for all inter-agent messages, replacing freetext signals. Defined in skills/message-schema/SKILL.md. -type: project ---- - -Formal inter-agent communication schema implemented via the `message-schema` skill. - -**What:** All agent output and orchestrator dispatch uses YAML frontmatter envelopes with a `signal` field as the primary routing key. 12 message types cover worker submissions, review/audit verdicts, triage/plan results, research results, and orchestrator commands. - -**Why:** Freetext signals (RFR, LGTM, VERDICT: PASS) were ambiguous and required prose parsing. Typed envelopes give the orchestrator a consistent, unambiguous routing key. - -**How to apply:** Every agent loads the `message-schema` skill. The qa-checklist includes schema compliance checks. The orchestrate skill routes by reading the `signal` field from agent output envelopes. diff --git a/.claude/plans/template-based-dual-target-generator.md b/.claude/plans/template-based-dual-target-generator.md deleted file mode 100644 index 15c2636..0000000 --- a/.claude/plans/template-based-dual-target-generator.md +++ /dev/null @@ -1,338 +0,0 @@ ---- -date: 2026-04-02 -task: template-based dual-target generator -tier: 2 -status: active ---- - -## Plan: Template-based dual-target generator - -## Summary - -Refactor agent-team from "Claude source of truth with a Codex converter" to "tool-agnostic templates with a generator that produces both Claude and Codex output." Agent bodies gain `${VAR}` placeholders for tool-specific references. Skills and rules are made tool-agnostic by replacing Claude-specific tool names and paths with generic language. A new `generate.sh` replaces `generate-codex.sh` and produces both `claude/` and `codex/` output directories. `install.sh` changes to symlink from the generated `claude/` directory. The orchestrate skill stays Claude-only since it is deeply tied to Claude's Agent tool dispatch model. - -## Out of Scope - -- Changing agent frontmatter schema (YAML fields stay as-is; the generator handles YAML-to-TOML conversion) -- Adding new agents or skills -- Changing the orchestrate skill's content (it stays Claude-only, not templated) -- Changing conventions skill content (already tool-agnostic) -- Modifying the message-schema envelope format -- Codex config.toml generation (the current `generate-codex.sh` logic for mapping model/effort/sandbox is preserved, not redesigned) -- README content updates beyond what's needed to reflect the new structure - -## Research Findings - -**envsubst scoping:** `envsubst '${PLANS_DIR} ${WEB_SEARCH}'` substitutes only the listed variables, leaving other `$` references untouched. Safe for use in files with YAML frontmatter and bash-like content. - -**Codex path conventions:** Codex has no `.claude/plans/` equivalent -- plans are regular files at `plans/` (project-relative). No `.claude/memory/` -- use `memory/` or omit. Skills are discovered from `~/.agents/skills/` via `SKILL.md` auto-matching or `skills.config` in agent TOML. Skills are NOT inlined into `developer_instructions`. - -**Skill strategy:** Option B (make skills tool-agnostic) for most skills. Orchestrate stays Claude-only. Tool name references like "Use Read/Glob/Grep" add marginal value and can be replaced with generic language like "Search the codebase." - -## Codebase Analysis - -### Files to modify - -| File | Change | -|---|---| -| `agents/architect.md` | Replace `.claude/plans/` (lines 19, 106) with `${PLANS_DIR}` | -| `agents/reviewer.md` | Replace `via WebFetch/WebSearch` (line 33) with `${WEB_SEARCH}` | -| `agents/debugger.md` | Replace `Use Grep` (line 25) with `${SEARCH_TOOLS}` | -| `agents/documenter.md` | Replace `Use Read/Glob/Grep` (line 29) with `${SEARCH_TOOLS}` | -| `skills/message-schema/SKILL.md` | Replace `.claude/plans/` (lines 155, 205) with `${PLANS_DIR}` | -| `skills/project/SKILL.md` | Replace `.claude/skills/project.md` (lines 7, 9) with `${PROJECT_SKILL_PATH}` | -| `skills/worker-protocol/SKILL.md` | Replace `Read/Glob/Grep` (line 50) with generic language | -| `skills/qa-checklist/SKILL.md` | Replace `Read/Grep` (line 13) with generic language | -| `rules/01-session.md` | Replace `.claude/memory/` (lines 3, 9, 12) with `${MEMORY_DIR}` | -| `rules/04-tools.md` | Replace `suggest /clear` (line 17) with `${CLEAR_CMD}` | -| `generate-codex.sh` | **Delete** -- replaced by `generate.sh` | -| `generate.sh` | **New** -- produces both `claude/` and `codex/` | -| `install.sh` | Rewire Claude symlinks to point at `claude/` output directory | -| `.gitignore` | Add `claude/` to exclusions | -| `flake.nix` | Add `envsubst` (from `gettext`) to devShell packages | - -### Files for context (read-only) - -| File | Why | -|---|---| -| `agents/worker.md` | Confirm no Claude-specific references in body (clean) | -| `agents/auditor.md` | Confirm no Claude-specific references in body (clean) | -| `agents/researcher.md` | Confirm no Claude-specific references in body (clean) | -| `skills/orchestrate/SKILL.md` | Confirm Claude-only decision (deeply coupled) | -| `skills/conventions/SKILL.md` | Confirm already tool-agnostic (clean) | -| `rules/02-responses.md` through `rules/07-research.md` | Confirm no Claude-specific references (clean, except 04-tools.md) | -| `codex/config.toml` | Understand current generated output (reference only) | - -### Current patterns - -- **Shell scripts** use `set -euo pipefail`, `SCRIPT_DIR` idiom, echo-based progress reporting -- **yq** (yq-go) is the YAML/JSON processor -- used for frontmatter extraction and settings parsing -- **Generated output** is committed to `codex/` but gitignored; `claude/` will follow the same pattern -- **Symlink strategy** in install.sh: directory symlinks for agents/skills/rules, file symlinks for individual config files; backup-on-conflict pattern -- **Agent markdown** uses YAML frontmatter with specific schema fields (`name`, `description`, `model`, `effort`, `permissionMode`, `tools`, `disallowedTools`, `maxTurns`, `skills`, `isolation`, `background`, `memory`) - -## Interface Contracts - -### Module ownership - -- `generate.sh`: owned by Step 5 (Wave 3), responsible for template expansion + output generation -- `agents/*.md` templates: owned by Steps 1-2 (Wave 1), responsible for adding `${VAR}` placeholders -- `skills/` tool-agnostic edits: owned by Step 3 (Wave 1), responsible for removing tool-specific language -- `rules/` tool-agnostic edits: owned by Step 4 (Wave 1), responsible for removing tool-specific paths -- `install.sh`: owned by Step 6 (Wave 3), responsible for rewiring symlink sources -- `.gitignore` + `flake.nix`: owned by Step 7 (Wave 2), support infrastructure - -### Shared interfaces - -**Template variable contract** -- all workers must use exactly these variable names and nothing else: - -```bash -# Variable definitions used by generate.sh for envsubst -# Claude target -PLANS_DIR=".claude/plans" -WEB_SEARCH="via WebFetch/WebSearch" -SEARCH_TOOLS="Use Grep/Glob/Read" -CLEAR_CMD="suggest /clear" -MEMORY_DIR=".claude/memory" -PROJECT_SKILL_PATH=".claude/skills/project.md" - -# Codex target -PLANS_DIR="plans" -WEB_SEARCH="via web search" -SEARCH_TOOLS="Search the codebase" -CLEAR_CMD="suggest starting a new session" -MEMORY_DIR="memory" -PROJECT_SKILL_PATH=".agents/skills/project/SKILL.md" -``` - -**Agent body extraction pattern** (preserved from `generate-codex.sh`): -```bash -# Extract body after closing --- of frontmatter -awk 'BEGIN{fm=0} /^---$/{if(fm==0){fm=1;next} if(fm==1){fm=2;next}} fm==2{print}' "$src_file" -``` - -**Claude output directory structure:** -``` -claude/ -├── agents/ # Expanded .md files (frontmatter preserved, body substituted) -├── CLAUDE.md # Copied from source -├── settings.json # Copied from source -├── rules -> ../rules # Symlink to shared rules (already tool-agnostic after Wave 1) -└── skills -> ../skills # Symlink to shared skills (already tool-agnostic after Wave 1) -``` - -**Codex output directory structure:** -``` -codex/ -├── agents/ # Generated .toml files (body substituted with Codex values) -├── AGENTS.md # Generated from CLAUDE.md + expanded rules -└── config.toml # Generated from settings.json -``` - -### Conventions for this task - -- Error handling: `set -euo pipefail` in all shell scripts. Echo progress for each major operation. Non-zero exit on failure. -- Naming: `${UPPER_SNAKE_CASE}` for template variables. `kebab-case` for file names. -- Template markers: Use `${VAR}` syntax only. Never use `$VAR` (ambiguous with shell) or `{{VAR}}` (not envsubst-compatible). -- Skill/rule edits: Replace tool-specific language with generic equivalents. Do NOT add `${VAR}` placeholders to skills or rules -- they are made tool-agnostic directly, not templated. Only agent bodies and message-schema (which references plan paths in examples) use template variables. - -**Correction on skill/rule strategy:** Skills and rules become tool-agnostic by direct edit (hardcoded generic language). They are then shared as-is between both targets via symlinks from the output directories. Template variables (`${VAR}`) are used ONLY in agent body markdown and in message-schema's example paths. This keeps the template surface minimal. - -However, `rules/01-session.md` and `rules/04-tools.md` present a problem: their content differs between Claude and Codex (`.claude/memory/` vs `memory/`, `/clear` vs "new session"). Two approaches: - -**Approach A -- Template the rules too:** Add `${VAR}` placeholders to rules and expand them per-target. This means rules can't be symlinked; they must be copied into each output directory. -**Approach B -- Make rules fully generic:** Use tool-agnostic language ("the project memory directory", "suggest clearing context"). No placeholders needed; rules stay shared. - -**Decision: Approach B.** The rules are guidance for agent behavior, not config. Generic language ("the project memory directory at the project root") communicates the intent without coupling to a specific path. The exact path is already established by the agent body templates and skills. This keeps the template surface to just agent bodies + message-schema examples. - -**Revised skill/rule edit strategy:** -- `rules/01-session.md`: Replace `.claude/memory/` with `memory/` (tool-agnostic path). This works for both targets because the rules describe conceptual behavior ("persist in the memory directory"), and the actual path resolution happens in agent instructions. -- `rules/04-tools.md`: Replace `suggest /clear` with `suggest clearing context or starting a new session` (tool-agnostic). -- `skills/worker-protocol/SKILL.md`: Replace `use Read/Glob/Grep directly` with `verify by reading the relevant files directly` (tool-agnostic). -- `skills/qa-checklist/SKILL.md`: Replace `Verify with Read/Grep if uncertain` with `Verify by reading the code if uncertain` (tool-agnostic). -- `skills/project/SKILL.md`: This one references a concrete file path (`.claude/skills/project.md`). Two options: (a) make it generic ("check for a project-specific skill file in the standard location"), or (b) template it. Since the path genuinely differs between tools and is specific enough to matter, **template it** with `${PROJECT_SKILL_PATH}`. This means the project skill gets expanded per-target, not symlinked. But since skills are directory-symlinked as a whole, we need a different approach: generate only this one skill per-target, or restructure. - -**Revised approach for project skill:** The simplest solution is to make the path generic. The project skill says "check for a project-specific skill file" -- the agent already knows where to look because each tool has its own conventions. Change the instruction to: "Check for a project-specific skill file in the current working directory's configuration. For Claude Code, this is `.claude/skills/project.md`. For Codex, this is discovered via the standard skill path." Actually this leaks tool awareness into the shared file. - -**Final decision for project skill:** Make it fully generic: "Before starting any work, check for a project-specific skill file in the current working directory. The location depends on the tool configuration." The concrete path is not needed -- each tool resolves its own skill paths. The skill's purpose is behavioral ("check for project context before starting"), not path-specific. - -Similarly for message-schema `plan_file` examples: these show `.claude/plans/kebab-case-title.md` as an example value. For Codex, this would be `plans/kebab-case-title.md`. Since message-schema is loaded as a skill (shared), we should either template it or make the example generic. **Decision:** Use `plans/kebab-case-title.md` as the example (dropping the `.claude/` prefix). This is the tool-agnostic path. Claude's architect agent body already specifies the full `.claude/plans/` path, so the schema example doesn't need to repeat the tool-specific prefix. - -**Final template variable list (reduced):** - -Only agent body markdown files use `${VAR}` placeholders. Everything else is made tool-agnostic by direct edit. - -| Variable | Claude value | Codex value | Used in | -|---|---|---|---| -| `${PLANS_DIR}` | `.claude/plans` | `plans` | architect.md body | -| `${WEB_SEARCH}` | `via WebFetch/WebSearch` | `via web search` | reviewer.md body | -| `${SEARCH_TOOLS}` | `Use Grep/Glob/Read` | `Search the codebase` | debugger.md body, documenter.md body | - -Skills, rules, and message-schema are made tool-agnostic by direct edit (no placeholders). - -## Approach - -**Strategy:** Two-layer approach. - -1. **Layer 1 -- Tool-agnostic shared content.** Skills and rules are edited to remove Claude-specific tool names and paths. They become shared infrastructure, symlinked from both output directories. - -2. **Layer 2 -- Templated agent bodies.** Agent markdown files in `agents/` gain `${VAR}` placeholders in their body text (not frontmatter). `generate.sh` expands these with tool-specific values and writes the results to `claude/` and `codex/`. - -The generator (`generate.sh`) replaces `generate-codex.sh` and handles both targets: -- **Claude target:** Expand templates with Claude values, copy frontmatter-intact agent .md files to `claude/agents/`, copy CLAUDE.md and settings.json, symlink to shared skills/rules. -- **Codex target:** Expand templates with Codex values, convert YAML frontmatter to TOML (preserving existing model/effort/sandbox mapping logic), generate AGENTS.md from CLAUDE.md + expanded rules, generate config.toml from settings.json. - -**Alternative considered: Jinja/m4 templating.** Rejected -- envsubst is simpler, already available via gettext in Nix, and sufficient for the ~3 variable substitutions in agent bodies. The complexity of Jinja (conditional blocks, filters) is not needed. - -**Alternative considered: Keep Claude agents as source, derive Codex only.** This is the current approach. Rejected because it means the "source" files contain Claude-specific references that leak into Codex output (the current bug this refactor fixes). Making the source tool-agnostic eliminates the class of bugs where a Codex agent says "Use Grep" or references `.claude/plans/`. - -## Risks & Gotchas - -1. **envsubst touching unintended `$` in agent bodies.** Mitigated by using the scoped form: `envsubst '${PLANS_DIR} ${WEB_SEARCH} ${SEARCH_TOOLS}'`. Only listed variables are substituted. The architect body contains `$` in example YAML blocks, which must NOT be substituted. - -2. **YAML frontmatter containing `$`.** The frontmatter is not passed through envsubst -- only the body. The generator extracts frontmatter and body separately, expands only the body, then reassembles. - -3. **Skills/rules shared as symlinks -- edit affects both targets immediately.** This is intentional. The skills and rules are tool-agnostic after Wave 1, so sharing is correct. But if someone adds a Claude-specific reference to a shared skill later, it leaks to Codex. The README should document this constraint. - -4. **Codex config.toml gets overwritten.** The user's `codex/config.toml` has been manually edited (different content than what generate-codex.sh produces). `generate.sh` will overwrite it. Mitigation: document that `codex/config.toml` is generated and should not be hand-edited. User customizations should go in the source `settings.json`. - -5. **install.sh behavior change.** Currently installs directly from `agents/` source. After this change, it installs from `claude/agents/` (generated). Users must run `generate.sh` before `install.sh`. The install script should check for this and error with guidance. - -6. **orchestrate skill references `.claude/plans/` paths.** This is acceptable -- orchestrate is Claude-only (not used by Codex). The skill is still shared via the skills directory symlink, but Codex agents don't load it (it's not in their skills list). - -## Risk Tags - -breaking-change (install.sh workflow changes: generate.sh must run first), data-mutation (generates files to claude/ and codex/ directories) - -## Implementation Waves - -### Wave 1 -- Make skills and rules tool-agnostic (4 parallel tasks) - -These are independent edits to different files. No task depends on another. - -- [ ] **Step 1: Template agent bodies** -- Add `${VAR}` placeholders to agent markdown files. - - `agents/architect.md`: Replace `.claude/plans/<kebab-case-title>.md` with `${PLANS_DIR}/<kebab-case-title>.md` (line 19) and `.claude/plans/kebab-case-title.md` with `${PLANS_DIR}/kebab-case-title.md` (line 106). There is also a `.claude/plans/` reference on line 69 inside the orchestrator's resume instruction -- replace that too. Verify no other `.claude/` references exist in the body. - - `agents/reviewer.md`: Replace `via WebFetch/WebSearch` with `${WEB_SEARCH}` (line 33). - - `agents/debugger.md`: Replace `Use Grep to find the relevant code` with `${SEARCH_TOOLS} to find the relevant code` (line 25). Verify the surrounding sentence reads naturally. - - `agents/documenter.md`: Replace `Use Read/Glob/Grep to understand the actual behavior` with `${SEARCH_TOOLS} to understand the actual behavior` (line 29). - -- [ ] **Step 2: Make message-schema tool-agnostic** -- Edit `skills/message-schema/SKILL.md`. - - Replace `plan_file: .claude/plans/kebab-case-title.md` with `plan_file: plans/kebab-case-title.md` (lines 155, 205). This is an example value in the schema, not a literal config -- using the generic path is correct. - - Do NOT change the envelope structure or field names. - -- [ ] **Step 3: Make skills tool-agnostic** -- Edit skills that contain Claude-specific tool names. - - `skills/worker-protocol/SKILL.md` line 50: Replace `use Read/Glob/Grep directly. Don't guess at file contents — verify.` with `verify by reading the relevant files. Don't guess at file contents.` - - `skills/qa-checklist/SKILL.md` line 13: Replace `Verify with Read/Grep if uncertain.` with `Verify by reading the code if uncertain.` - - `skills/project/SKILL.md` lines 7, 9: Replace `.claude/skills/project.md` with `a project-specific skill file`. Rewrite the two sentences: - - Line 7: "Before starting any work, check for a project-specific skill file in the current working directory's tool configuration." - - Line 9: "If one exists, read it and treat its contents as additional instructions..." - - Do NOT edit `skills/orchestrate/SKILL.md` (stays Claude-only) or `skills/conventions/SKILL.md` (already clean). - -- [ ] **Step 4: Make rules tool-agnostic** -- Edit rules with Claude-specific references. - - `rules/01-session.md`: Replace all three occurrences of `.claude/memory/` with `memory/`. Update surrounding prose if needed for clarity. The CLAUDE.md hierarchy reference on line 3 is fine -- it's a generic concept name, not a file path (each tool has its own hierarchy). - - `rules/04-tools.md` line 17: Replace `suggest /clear` with `suggest clearing context or starting a new session`. - -### Wave 2 -- Infrastructure (depends on Wave 1 for knowing the final variable list) - -- [ ] **Step 5: Update .gitignore and flake.nix** -- Support infrastructure for the new generator. - - `.gitignore`: Add `claude/` line (generated output, same treatment as `codex/`). Keep the existing `codex/` line. - - `flake.nix`: Add `pkgs.gettext` to the devShell packages list (provides `envsubst`). Keep existing `pkgs.yq-go` and `pkgs.codex`. - -### Wave 3 -- Generator and installer (depends on Wave 1 for templates, Wave 2 for infrastructure) - -- [ ] **Step 6: Write generate.sh** -- New unified generator replacing `generate-codex.sh`. - - Location: `generate.sh` (project root, same level as old `generate-codex.sh`) - - Delete `generate-codex.sh` (or rename -- but deleting is cleaner since the new script fully replaces it) - - **Claude target generation:** - 1. Create `claude/agents/` directory - 2. For each `agents/*.md`: extract frontmatter and body separately. Run body through `envsubst '${PLANS_DIR} ${WEB_SEARCH} ${SEARCH_TOOLS}'` with Claude variable values. Reassemble frontmatter + expanded body. Write to `claude/agents/<name>.md`. - 3. Copy `CLAUDE.md` to `claude/CLAUDE.md` - 4. Copy `settings.json` to `claude/settings.json` - 5. Create relative symlinks: `claude/rules -> ../rules`, `claude/skills -> ../skills` - - **Codex target generation** (preserve all existing logic from `generate-codex.sh`): - 1. Create `codex/agents/` directory - 2. For each `agents/*.md`: extract frontmatter and body. Run body through `envsubst '${PLANS_DIR} ${WEB_SEARCH} ${SEARCH_TOOLS}'` with Codex variable values. Convert frontmatter to TOML format (same mapping functions: `map_model`, `map_effort`, `map_sandbox_mode`). Write to `codex/agents/<name>.toml`. - 3. Generate `codex/AGENTS.md` from `CLAUDE.md` + rules/*.md (same logic as current `generate-codex.sh`) - 4. Generate `codex/config.toml` from `settings.json` (same logic as current) - - **Script structure:** - ```bash - #!/usr/bin/env bash - set -euo pipefail - - SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" - - # --- Variable definitions --- - # Claude target - declare -A CLAUDE_VARS=( - [PLANS_DIR]=".claude/plans" - [WEB_SEARCH]="via WebFetch/WebSearch" - [SEARCH_TOOLS]="Use Grep/Glob/Read" - ) - # Codex target - declare -A CODEX_VARS=( - [PLANS_DIR]="plans" - [WEB_SEARCH]="via web search" - [SEARCH_TOOLS]="Search the codebase" - ) - - # --- Shared functions --- - extract_frontmatter() { ... } # yq --front-matter=extract - extract_body() { ... } # awk pattern from current script - expand_body() { ... } # envsubst with scoped variable list - - # --- Claude generation --- - generate_claude() { ... } - - # --- Codex generation (ported from generate-codex.sh) --- - generate_codex() { ... } - - generate_claude - generate_codex - echo "Done. Run ./install.sh to link into tool directories." - ``` - - **Key detail for envsubst scoping:** The `expand_body` function must export only the target's variables, run envsubst with the explicit variable list, then unset them. This prevents cross-contamination and protects other `$` references in the body. - ```bash - expand_body() { - local body="$1" - shift - # Remaining args are KEY=VALUE pairs - local var_list="" - for pair in "$@"; do - local key="${pair%%=*}" - local val="${pair#*=}" - export "$key=$val" - var_list+=" \${$key}" - done - echo "$body" | envsubst "$var_list" - for pair in "$@"; do - unset "${pair%%=*}" - done - } - ``` - -- [ ] **Step 7: Update install.sh** -- Rewire to use generated output. - - **Claude installation:** Change `AGENTS_SRC` from `$SCRIPT_DIR/agents` to `$SCRIPT_DIR/claude/agents`. Change `CLAUDE_MD_SRC` from `$SCRIPT_DIR/CLAUDE.md` to `$SCRIPT_DIR/claude/CLAUDE.md`. Change `SETTINGS_SRC` from `$SCRIPT_DIR/settings.json` to `$SCRIPT_DIR/claude/settings.json`. Skills and rules still symlink from source (they're tool-agnostic and shared): `SKILLS_SRC="$SCRIPT_DIR/skills"`, `RULES_SRC="$SCRIPT_DIR/rules"` (unchanged). - - **Pre-flight check:** At the top of install.sh, verify `claude/` directory exists. If not, print: `"Error: claude/ not found. Run ./generate.sh first."` and exit 1. - - **Codex installation:** Change `codex/agents` source to `$SCRIPT_DIR/codex/agents` (already correct). Keep all other Codex paths the same. - - Preserve the entire symlink helper infrastructure (create_symlink, create_file_symlink, OS detection, backup logic). - -### Wave 4 -- Documentation and cleanup (depends on Wave 3) - -- [ ] **Step 8: Update README.md** -- Reflect the new workflow. - - Quick install section: add `./generate.sh` before `./install.sh` - - Codex CLI compatibility section: update to reflect `generate.sh` replaces `generate-codex.sh` and now generates both targets - - "What gets generated" table: add Claude row showing `agents/*.md` -> `claude/agents/*.md` - - Add a note about template variables and the tool-agnostic constraint on shared skills/rules - - Remove references to `generate-codex.sh` - -## Acceptance Criteria - -1. `./generate.sh` produces `claude/agents/*.md` with Claude-specific values expanded (`.claude/plans/`, `Use Grep/Glob/Read`, etc.) -- verified by: grep the generated files for expanded values, confirm no `${` remains -2. `./generate.sh` produces `codex/agents/*.toml` with Codex-specific values expanded (`plans/`, `Search the codebase`, etc.) -- verified by: grep the generated files for expanded values, confirm no `${` remains -3. No Claude-specific tool names (`Read`, `Glob`, `Grep`, `WebFetch`, `WebSearch`, `Edit`, `Write`) appear in skills (except orchestrate) or rules -- verified by: grep shared skills and rules for these tool names -4. No `.claude/` paths appear in skills (except orchestrate) or rules -- verified by: grep shared skills and rules for `.claude/` -5. `./install.sh` errors with a helpful message if `claude/` does not exist -- verified by: manual test -6. `./install.sh` successfully symlinks from `claude/` when it exists -- verified by: manual test -7. Codex output is functionally identical to what `generate-codex.sh` produced (same TOML structure, same model/effort/sandbox mappings) except with Codex-specific values substituted -- verified by: diff old and new codex/ output -8. The `envsubst` expansion does NOT touch `$` characters in YAML frontmatter or example code blocks -- verified by: inspect architect.md generated output for intact `$` in YAML examples -9. `flake.nix` devShell includes `gettext` (provides envsubst) -- verified by: `nix develop -c which envsubst` diff --git a/.gitignore b/.gitignore index 4c34028..466ff5b 100644 --- a/.gitignore +++ b/.gitignore @@ -12,3 +12,5 @@ Thumbs.db # Generated output (derived from source templates via generate.sh) claude/ codex/ +.claude +.codex diff --git a/README.md b/README.md index 152c2ed..c56b495 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # agent-team -A portable Claude Code agent team configuration. Clone it, run `install.sh`, and your Claude Code sessions get a full team of specialized subagents and shared skills — on any machine. +A portable agent team configuration for Claude Code and Codex CLI. Clone it, run `./generate.sh` + `./install.sh`, and both tools get a full team of specialized subagents and shared skills — on any machine. ## Quick install @@ -12,7 +12,7 @@ nix develop # enter devShell with yq + envsubst ./install.sh # symlinks into ~/.claude/ and ~/.codex/ (if present) ``` -The scripts generate configuration for both Claude Code and Codex CLI (if `~/.codex/` exists), then symlink agents, skills, rules, CLAUDE.md, and settings.json into `~/.claude/`. Works on Linux, macOS, and Windows (Git Bash). +`generate.sh` expands shared agent templates into tool-specific outputs for Claude Code and Codex CLI. `install.sh` symlinks the results into `~/.claude/` and `~/.codex/`. Works on Linux, macOS, and Windows (Git Bash). ## Maintenance @@ -47,7 +47,9 @@ Global instructions are modularized in `rules/` and auto-loaded by Claude Code f ## How to use -In an interactive Claude Code session, load the orchestrate skill when a task is complex enough to warrant delegation: +### Claude Code + +Load the orchestrate skill when a task is complex enough to warrant delegation: ``` /skill orchestrate @@ -55,23 +57,23 @@ In an interactive Claude Code session, load the orchestrate skill when a task is Once loaded, Claude acts as orchestrator — decomposing tasks, selecting agents, reviewing output, and managing the git flow. Agents are auto-delegated based on task type; you don't invoke them directly. -For simple tasks, agents can be invoked directly: +For simple tasks, invoke an agent directly: ``` /agent worker Fix the broken pagination in the user list endpoint ``` -## Codex CLI compatibility +### Codex CLI -This project also generates configuration for [OpenAI Codex CLI](https://github.com/openai/codex). Claude Code config is the source of truth; Codex config is derived from it. +Agents are available as named agents in `~/.codex/agents/`. Invoke them with: -### Setup - -```bash -nix develop # enter devShell with yq + envsubst -./generate.sh # generate Claude + Codex config from templates -./install.sh # installs both Claude and Codex (if ~/.codex exists) ``` +codex --agent worker "Fix the broken pagination in the user list endpoint" +``` + +## Dual-target generation + +Agent source files in `agents/` are the single source of truth. `generate.sh` derives tool-specific outputs for both Claude Code and [OpenAI Codex CLI](https://github.com/openai/codex). ### What gets generated @@ -83,7 +85,7 @@ nix develop # enter devShell with yq + envsubst | `settings.json` | `codex/config.toml` | `~/.codex/config.toml` | | `skills/` | (shared as-is) | `~/.claude/skills/` + `~/.agents/skills/` | -### Model mapping +## Model mapping | Claude Code | Codex CLI | |---|---| @@ -91,7 +93,7 @@ nix develop # enter devShell with yq + envsubst | `sonnet` | `gpt-5.3-codex` | | `haiku` | `gpt-5.1-codex-mini` | -### Template variables +## Template variables Agent body text uses `${VAR}` placeholders that are expanded per-target by `generate.sh`: diff --git a/install.sh b/install.sh index dc63bf9..b3be864 100755 --- a/install.sh +++ b/install.sh @@ -1,7 +1,7 @@ #!/usr/bin/env bash set -euo pipefail -# install.sh — symlinks agent-team into ~/.claude/ +# install.sh — symlinks agent-team into ~/.claude/ and ~/.codex/ (if present) # Works on Windows (Git Bash/MSYS2), Linux, and macOS. SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" From 84882d3b9cd60922006c25b355089a0d190caa30 Mon Sep 17 00:00:00 2001 From: Bryan Ramos <bryan@ramos.codes> Date: Thu, 2 Apr 2026 13:25:19 -0400 Subject: [PATCH 30/36] feat(protocol): add authored runtime and team configs --- SETTINGS.yaml | 51 +++ TEAM.yaml | 276 +++++++++++++++ schemas/agent-runtime.schema.json | 162 +++++++++ schemas/team.schema.json | 544 ++++++++++++++++++++++++++++++ spec/agent-runtime-v1.md | 105 ++++++ spec/team-protocol-v1.md | 136 ++++++++ 6 files changed, 1274 insertions(+) create mode 100644 SETTINGS.yaml create mode 100644 TEAM.yaml create mode 100644 schemas/agent-runtime.schema.json create mode 100644 schemas/team.schema.json create mode 100644 spec/agent-runtime-v1.md create mode 100644 spec/team-protocol-v1.md diff --git a/SETTINGS.yaml b/SETTINGS.yaml new file mode 100644 index 0000000..e5cd4f5 --- /dev/null +++ b/SETTINGS.yaml @@ -0,0 +1,51 @@ +# yaml-language-server: $schema=./schemas/agent-runtime.schema.json + +version: 1 + +model: + class: balanced + reasoning: medium + +runtime: + filesystem: workspace-write + approval: guarded-auto + network_access: false + tools: + - shell + - read + - edit + - write + - glob + - grep + - web_fetch + - web_search + +safety: + protected_paths: + - ~/.ssh/** + - ~/.aws/** + - ~/.gnupg/** + - "**/.env*" + dangerous_shell_commands: + ask: + - rm * + - rmdir * + - git push --force* + - git push -f* + - git reset --hard* + - git clean * + - chmod * + - dd * + - mkfs* + - shred * + - kill * + - killall * + - sudo * + +targets: + claude: + claude_md_excludes: + - .claude/agent-memory/** + codex: + approval_policy: untrusted + network_access: false diff --git a/TEAM.yaml b/TEAM.yaml new file mode 100644 index 0000000..e8b8587 --- /dev/null +++ b/TEAM.yaml @@ -0,0 +1,276 @@ +version: 1 + +agents: + order: + - architect + - auditor + - debugger + - documenter + - researcher + - reviewer + - worker + items: + architect: + id: architect + name: architect + description: Research-first planning agent. Handles triage, research coordination, architecture design, and wave decomposition. Use before any non-trivial implementation task. Produces the implementation blueprint the entire team follows. + model: opus + effort: max + permission_mode: plan + tools: + - Read + - Glob + - Grep + - WebFetch + - WebSearch + - Bash + - Write + disallowed_tools: [] + max_turns: 35 + skills: + - conventions + - message-schema + instruction_file: agents/architect.md + auditor: + id: auditor + name: auditor + description: Use after implementation — audits for security vulnerabilities and validates runtime behavior. Builds, tests, and probes acceptance criteria. Never modifies code. + model: sonnet + effort: "" + permission_mode: "" + tools: + - Read + - Glob + - Grep + - Bash + - WebFetch + - WebSearch + disallowed_tools: + - Write + - Edit + max_turns: 25 + skills: + - conventions + - message-schema + - qa-checklist + background: true + instruction_file: agents/auditor.md + debugger: + id: debugger + name: debugger + description: Use immediately when encountering a bug, error, or unexpected behavior. Diagnoses root cause and applies a minimal targeted fix. Does not refactor or improve surrounding code. + model: sonnet + effort: "" + permission_mode: acceptEdits + tools: + - Read + - Write + - Edit + - Glob + - Grep + - Bash + disallowed_tools: [] + max_turns: 20 + skills: + - conventions + - worker-protocol + - message-schema + - qa-checklist + instruction_file: agents/debugger.md + documenter: + id: documenter + name: documenter + description: Use when asked to write or update documentation — READMEs, API references, architecture overviews, inline doc comments, or changelogs. Reads code first, writes accurate docs. Never modifies source code. + model: sonnet + effort: high + permission_mode: "" + tools: + - Read + - Write + - Edit + - Glob + - Grep + - Bash + disallowed_tools: [] + max_turns: 20 + skills: + - conventions + - worker-protocol + - message-schema + - qa-checklist + memory: project + instruction_file: agents/documenter.md + researcher: + id: researcher + name: researcher + description: Use to answer a specific research question with verified facts. Spawned in parallel — one instance per topic. Stateless. Returns verified facts, source URLs, and gotchas. + model: sonnet + effort: "" + permission_mode: plan + tools: + - Read + - Glob + - Grep + - Bash + - WebFetch + - WebSearch + disallowed_tools: + - Write + - Edit + max_turns: 10 + skills: + - message-schema + instruction_file: agents/researcher.md + reviewer: + id: reviewer + name: reviewer + description: Use after implementation — reviews code quality and verifies claims against source, docs, and acceptance criteria. Never modifies code. + model: sonnet + effort: "" + permission_mode: plan + tools: + - Read + - Glob + - Grep + - Bash + - WebFetch + - WebSearch + disallowed_tools: + - Write + - Edit + max_turns: 20 + skills: + - conventions + - message-schema + - qa-checklist + instruction_file: agents/reviewer.md + worker: + id: worker + name: worker + description: Universal implementer. Handles all task tiers — trivial to architectural. Model is scaled by the orchestrator based on task complexity (haiku for trivial, sonnet for standard, opus for architectural/ambiguous). Default implementer for all implementation work. + model: sonnet + effort: "" + permission_mode: acceptEdits + tools: + - Read + - Write + - Edit + - Glob + - Grep + - Bash + disallowed_tools: [] + max_turns: 25 + skills: + - conventions + - worker-protocol + - message-schema + - qa-checklist + isolation: worktree + instruction_file: agents/worker.md + +skills: + order: + - conventions + - message-schema + - orchestrate + - qa-checklist + - worker-protocol + items: + conventions: + id: conventions + name: conventions + description: Core coding conventions and quality priorities for all projects. + instruction_file: skills/conventions/SKILL.md + applies_to: + - claude + - codex + install_mode: shared + message-schema: + id: message-schema + name: message-schema + description: Typed envelope schema for all inter-agent communication. Defines message types, required fields, and signal routing contracts. + instruction_file: skills/message-schema/SKILL.md + applies_to: + - claude + - codex + install_mode: shared + orchestrate: + id: orchestrate + name: orchestrate + description: Orchestration framework for decomposing and delegating complex tasks to the agent team. Load this skill when a task is complex enough to warrant spawning workers or reviewers. Covers task tiers, planning pipeline, wave dispatch, review, and git flow. + instruction_file: skills/orchestrate/SKILL.md + applies_to: + - claude + - codex + install_mode: shared + qa-checklist: + id: qa-checklist + name: qa-checklist + description: Self-validation checklist. All workers run this against their own output before returning results. + instruction_file: skills/qa-checklist/SKILL.md + applies_to: + - claude + - codex + install_mode: shared + worker-protocol: + id: worker-protocol + name: worker-protocol + description: Standard output format, feedback handling, and operational procedures for all worker agents. + instruction_file: skills/worker-protocol/SKILL.md + applies_to: + - claude + - codex + install_mode: shared + +rules: + order: + - 01-session + - 02-responses + - 03-git + - 04-tools + - 05-verification + - 06-nix + - 07-research + items: + 01-session: + id: 01-session + source_file: rules/01-session.md + applies_to: + - claude + - codex + 02-responses: + id: 02-responses + source_file: rules/02-responses.md + applies_to: + - claude + - codex + 03-git: + id: 03-git + source_file: rules/03-git.md + applies_to: + - claude + - codex + 04-tools: + id: 04-tools + source_file: rules/04-tools.md + applies_to: + - claude + - codex + 05-verification: + id: 05-verification + source_file: rules/05-verification.md + applies_to: + - claude + - codex + 06-nix: + id: 06-nix + source_file: rules/06-nix.md + applies_to: + - claude + - codex + 07-research: + id: 07-research + source_file: rules/07-research.md + applies_to: + - claude + - codex diff --git a/schemas/agent-runtime.schema.json b/schemas/agent-runtime.schema.json new file mode 100644 index 0000000..1a977d6 --- /dev/null +++ b/schemas/agent-runtime.schema.json @@ -0,0 +1,162 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://example.com/schemas/agent-runtime.schema.json", + "title": "Agent Runtime Config", + "description": "Portable runtime policy for deriving tool-specific AI harness configuration.", + "type": "object", + "additionalProperties": false, + "required": [ + "version", + "model", + "runtime", + "safety" + ], + "properties": { + "version": { + "type": "integer", + "const": 1 + }, + "model": { + "type": "object", + "additionalProperties": false, + "required": [ + "class", + "reasoning" + ], + "properties": { + "class": { + "type": "string", + "enum": [ + "fast", + "balanced", + "powerful" + ], + "description": "Portable model tier. Adapters map this to provider-specific model names." + }, + "reasoning": { + "type": "string", + "enum": [ + "low", + "medium", + "high", + "max" + ] + } + } + }, + "runtime": { + "type": "object", + "additionalProperties": false, + "required": [ + "filesystem", + "approval", + "network_access", + "tools" + ], + "properties": { + "filesystem": { + "type": "string", + "enum": [ + "read-only", + "workspace-write" + ] + }, + "approval": { + "type": "string", + "enum": [ + "manual", + "guarded-auto", + "full-auto" + ], + "description": "Portable approval intent. Adapters degrade where exact behavior is unavailable." + }, + "network_access": { + "type": "boolean" + }, + "tools": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "shell", + "read", + "edit", + "write", + "glob", + "grep", + "web_fetch", + "web_search" + ] + }, + "uniqueItems": true + } + } + }, + "safety": { + "type": "object", + "additionalProperties": false, + "required": [ + "protected_paths", + "dangerous_shell_commands" + ], + "properties": { + "protected_paths": { + "type": "array", + "items": { + "type": "string" + } + }, + "dangerous_shell_commands": { + "type": "object", + "additionalProperties": false, + "required": [ + "ask" + ], + "properties": { + "ask": { + "type": "array", + "items": { + "type": "string" + } + } + } + } + } + }, + "targets": { + "type": "object", + "additionalProperties": false, + "properties": { + "claude": { + "type": "object", + "additionalProperties": false, + "properties": { + "claude_md_excludes": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "codex": { + "type": "object", + "additionalProperties": false, + "properties": { + "approval_policy": { + "type": "string", + "enum": [ + "on-request", + "untrusted", + "never" + ] + }, + "network_access": { + "type": "boolean" + } + } + } + } + } + } +} diff --git a/schemas/team.schema.json b/schemas/team.schema.json new file mode 100644 index 0000000..1710b80 --- /dev/null +++ b/schemas/team.schema.json @@ -0,0 +1,544 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://example.com/schemas/team.schema.json", + "title": "Team Protocol Config", + "description": "Portable team-level inventory and metadata for agents, skills, and rules. For v1 this schema enforces strict inventory membership/order; generator runtime still validates referenced files exist on disk.", + "type": "object", + "additionalProperties": false, + "required": [ + "version", + "agents", + "skills", + "rules" + ], + "properties": { + "version": { + "type": "integer", + "const": 1 + }, + "agents": { + "$ref": "#/$defs/inventory_agents" + }, + "skills": { + "$ref": "#/$defs/inventory_skills" + }, + "rules": { + "$ref": "#/$defs/inventory_rules" + } + }, + "$defs": { + "id_agent": { + "type": "string", + "pattern": "^[a-z][a-z0-9-]*$" + }, + "id_skill": { + "type": "string", + "pattern": "^[a-z][a-z0-9-]*$" + }, + "id_rule": { + "type": "string", + "pattern": "^[0-9]{2}-[a-z0-9-]+$" + }, + "tool_name": { + "type": "string", + "enum": [ + "Read", + "Write", + "Edit", + "Glob", + "Grep", + "Bash", + "WebFetch", + "WebSearch" + ] + }, + "target_name": { + "type": "string", + "enum": [ + "claude", + "codex" + ] + }, + "agent_item": { + "type": "object", + "additionalProperties": false, + "required": [ + "id", + "name", + "description", + "model", + "effort", + "permission_mode", + "tools", + "disallowed_tools", + "max_turns", + "skills", + "instruction_file" + ], + "properties": { + "id": { + "$ref": "#/$defs/id_agent" + }, + "name": { + "type": "string", + "minLength": 1 + }, + "description": { + "type": "string", + "minLength": 1 + }, + "model": { + "type": "string", + "enum": [ + "haiku", + "sonnet", + "opus" + ] + }, + "effort": { + "type": "string", + "enum": [ + "", + "low", + "medium", + "high", + "max" + ] + }, + "permission_mode": { + "type": "string", + "enum": [ + "", + "plan", + "acceptEdits" + ] + }, + "tools": { + "type": "array", + "items": { + "$ref": "#/$defs/tool_name" + }, + "uniqueItems": true + }, + "disallowed_tools": { + "type": "array", + "items": { + "$ref": "#/$defs/tool_name" + }, + "uniqueItems": true + }, + "max_turns": { + "type": "integer", + "minimum": 1 + }, + "skills": { + "type": "array", + "items": { + "$ref": "#/$defs/id_skill" + }, + "uniqueItems": true + }, + "background": { + "type": "boolean" + }, + "memory": { + "type": "string", + "enum": [ + "project" + ] + }, + "isolation": { + "type": "string", + "enum": [ + "worktree" + ] + }, + "instruction_file": { + "type": "string", + "pattern": "^agents/[a-z0-9-]+\\.md$" + } + } + }, + "skill_item": { + "type": "object", + "additionalProperties": false, + "required": [ + "id", + "name", + "description", + "instruction_file", + "applies_to", + "install_mode" + ], + "properties": { + "id": { + "$ref": "#/$defs/id_skill" + }, + "name": { + "type": "string", + "minLength": 1 + }, + "description": { + "type": "string", + "minLength": 1 + }, + "instruction_file": { + "type": "string", + "pattern": "^skills/[a-z0-9-]+/SKILL\\.md$" + }, + "applies_to": { + "type": "array", + "items": { + "$ref": "#/$defs/target_name" + }, + "minItems": 1, + "uniqueItems": true + }, + "install_mode": { + "type": "string", + "enum": [ + "shared" + ] + } + } + }, + "rule_item": { + "type": "object", + "additionalProperties": false, + "required": [ + "id", + "source_file", + "applies_to" + ], + "properties": { + "id": { + "$ref": "#/$defs/id_rule" + }, + "source_file": { + "type": "string", + "pattern": "^rules/[0-9]{2}-[a-z0-9-]+\\.md$" + }, + "applies_to": { + "type": "array", + "items": { + "$ref": "#/$defs/target_name" + }, + "minItems": 1, + "uniqueItems": true + } + } + }, + "inventory_agents": { + "type": "object", + "additionalProperties": false, + "description": "Agent inventory for protocol v1. This schema enforces exact order, exact keys, and key/id equality for the current repository inventory.", + "required": [ + "order", + "items" + ], + "properties": { + "order": { + "type": "array", + "items": { + "$ref": "#/$defs/id_agent" + }, + "uniqueItems": true, + "const": [ + "architect", + "auditor", + "debugger", + "documenter", + "researcher", + "reviewer", + "worker" + ] + }, + "items": { + "type": "object", + "additionalProperties": false, + "required": [ + "architect", + "auditor", + "debugger", + "documenter", + "researcher", + "reviewer", + "worker" + ], + "properties": { + "architect": { + "allOf": [ + { "$ref": "#/$defs/agent_item" }, + { + "properties": { + "id": { "const": "architect" } + } + } + ] + }, + "auditor": { + "allOf": [ + { "$ref": "#/$defs/agent_item" }, + { + "properties": { + "id": { "const": "auditor" } + } + } + ] + }, + "debugger": { + "allOf": [ + { "$ref": "#/$defs/agent_item" }, + { + "properties": { + "id": { "const": "debugger" } + } + } + ] + }, + "documenter": { + "allOf": [ + { "$ref": "#/$defs/agent_item" }, + { + "properties": { + "id": { "const": "documenter" } + } + } + ] + }, + "researcher": { + "allOf": [ + { "$ref": "#/$defs/agent_item" }, + { + "properties": { + "id": { "const": "researcher" } + } + } + ] + }, + "reviewer": { + "allOf": [ + { "$ref": "#/$defs/agent_item" }, + { + "properties": { + "id": { "const": "reviewer" } + } + } + ] + }, + "worker": { + "allOf": [ + { "$ref": "#/$defs/agent_item" }, + { + "properties": { + "id": { "const": "worker" } + } + } + ] + } + } + } + } + }, + "inventory_skills": { + "type": "object", + "additionalProperties": false, + "description": "Skill inventory for protocol v1. This schema enforces exact order, exact keys, and key/id equality for the current repository inventory.", + "required": [ + "order", + "items" + ], + "properties": { + "order": { + "type": "array", + "items": { + "$ref": "#/$defs/id_skill" + }, + "uniqueItems": true, + "const": [ + "conventions", + "message-schema", + "orchestrate", + "qa-checklist", + "worker-protocol" + ] + }, + "items": { + "type": "object", + "additionalProperties": false, + "required": [ + "conventions", + "message-schema", + "orchestrate", + "qa-checklist", + "worker-protocol" + ], + "properties": { + "conventions": { + "allOf": [ + { "$ref": "#/$defs/skill_item" }, + { + "properties": { + "id": { "const": "conventions" } + } + } + ] + }, + "message-schema": { + "allOf": [ + { "$ref": "#/$defs/skill_item" }, + { + "properties": { + "id": { "const": "message-schema" } + } + } + ] + }, + "orchestrate": { + "allOf": [ + { "$ref": "#/$defs/skill_item" }, + { + "properties": { + "id": { "const": "orchestrate" } + } + } + ] + }, + "qa-checklist": { + "allOf": [ + { "$ref": "#/$defs/skill_item" }, + { + "properties": { + "id": { "const": "qa-checklist" } + } + } + ] + }, + "worker-protocol": { + "allOf": [ + { "$ref": "#/$defs/skill_item" }, + { + "properties": { + "id": { "const": "worker-protocol" } + } + } + ] + } + } + } + } + }, + "inventory_rules": { + "type": "object", + "additionalProperties": false, + "description": "Rule inventory for protocol v1. This schema enforces exact order, exact keys, and key/id equality for the current repository inventory.", + "required": [ + "order", + "items" + ], + "properties": { + "order": { + "type": "array", + "items": { + "$ref": "#/$defs/id_rule" + }, + "uniqueItems": true, + "const": [ + "01-session", + "02-responses", + "03-git", + "04-tools", + "05-verification", + "06-nix", + "07-research" + ] + }, + "items": { + "type": "object", + "additionalProperties": false, + "required": [ + "01-session", + "02-responses", + "03-git", + "04-tools", + "05-verification", + "06-nix", + "07-research" + ], + "properties": { + "01-session": { + "allOf": [ + { "$ref": "#/$defs/rule_item" }, + { + "properties": { + "id": { "const": "01-session" } + } + } + ] + }, + "02-responses": { + "allOf": [ + { "$ref": "#/$defs/rule_item" }, + { + "properties": { + "id": { "const": "02-responses" } + } + } + ] + }, + "03-git": { + "allOf": [ + { "$ref": "#/$defs/rule_item" }, + { + "properties": { + "id": { "const": "03-git" } + } + } + ] + }, + "04-tools": { + "allOf": [ + { "$ref": "#/$defs/rule_item" }, + { + "properties": { + "id": { "const": "04-tools" } + } + } + ] + }, + "05-verification": { + "allOf": [ + { "$ref": "#/$defs/rule_item" }, + { + "properties": { + "id": { "const": "05-verification" } + } + } + ] + }, + "06-nix": { + "allOf": [ + { "$ref": "#/$defs/rule_item" }, + { + "properties": { + "id": { "const": "06-nix" } + } + } + ] + }, + "07-research": { + "allOf": [ + { "$ref": "#/$defs/rule_item" }, + { + "properties": { + "id": { "const": "07-research" } + } + } + ] + } + } + } + } + } + } +} diff --git a/spec/agent-runtime-v1.md b/spec/agent-runtime-v1.md new file mode 100644 index 0000000..4bf333d --- /dev/null +++ b/spec/agent-runtime-v1.md @@ -0,0 +1,105 @@ +# Agent Runtime Config v1 + +`SETTINGS.yaml` is the human-authored source of truth for portable runtime intent in this repo. + +Team inventory metadata is defined separately in `TEAM.yaml` (see `spec/team-protocol-v1.md`). This spec only covers runtime policy. + +## Goals + +- Keep one editable config for approval, filesystem, network, and model intent. +- Generate backward-compatible Claude and Codex outputs from that shared intent. +- Make adapter lossiness explicit where provider config surfaces do not line up. + +## Scope + +Version 1 standardizes: + +- portable model tier and reasoning level +- filesystem access intent +- approval intent +- network access intent +- portable tool classes +- protected path rules +- dangerous shell command prompts +- target-specific escape hatches only when the target exposes settings with no shared equivalent + +Version 1 does not attempt to standardize: + +- every provider model name +- provider-specific tool grammars +- every future runtime capability for local agents, IDE plugins, or hosted agents + +## Shared fields + +### `model` + +- `class`: `fast | balanced | powerful` +- `reasoning`: `low | medium | high | max` + +### `runtime` + +- `filesystem`: `read-only | workspace-write` +- `approval`: `manual | guarded-auto | full-auto` +- `network_access`: boolean +- `tools`: portable tool classes such as `shell`, `read`, `edit`, `write`, `glob`, `grep`, `web_fetch`, `web_search` + +### `safety` + +- `protected_paths`: glob patterns that should remain blocked from normal reads or writes +- `dangerous_shell_commands.ask`: shell command patterns that should remain approval-gated + +### `targets` + +Target blocks are escape hatches, not the main schema. Use them only where a runtime exposes a knob with no shared equivalent. + +Current target-specific fields: + +- `targets.claude.claude_md_excludes` +- `targets.codex.approval_policy` +- `targets.codex.network_access` + +## Adapter rules + +### Claude Code + +`settings.json` is generated as a compatibility artifact. + +- `runtime.filesystem = read-only` -> `permissions.defaultMode = "plan"` +- `runtime.filesystem = workspace-write` -> `permissions.defaultMode = "acceptEdits"` +- `runtime.tools` -> Claude tool allow-list +- `safety.protected_paths` -> Claude `deny` entries for `Read`, `Write`, and `Edit` +- `dangerous_shell_commands.ask` -> Claude `ask` entries wrapped as `Bash(...)` + +Lossiness: + +- Claude vends `allow` / `deny` / `ask` as tool-pattern rules. +- Shared `approval` intent does not map 1:1 to Claude beyond `plan` vs `acceptEdits`. + +### Codex CLI + +`codex/config.toml` is generated directly from shared intent. + +- `runtime.filesystem = read-only` -> `sandbox_mode = "read-only"` +- `runtime.filesystem = workspace-write` -> `sandbox_mode = "workspace-write"` +- `runtime.approval = manual` -> `approval_policy = "on-request"` +- `runtime.approval = guarded-auto` -> `approval_policy = "untrusted"` +- `runtime.approval = full-auto` -> `approval_policy = "never"` +- `runtime.network_access` -> `[sandbox_workspace_write].network_access` + +Lossiness: + +- Codex does not expose Claude-style per-tool `allow` / `deny` / `ask` pattern controls in `config.toml`. +- Protected paths and dangerous command prompts are therefore only partially representable in Codex config today. + +## Compatibility contract + +The repo preserves these compatibility artifacts: + +- `settings.json` +- `claude/settings.json` +- `claude/CLAUDE.md` +- `codex/config.toml` +- `codex/AGENTS.md` +- generated agent outputs for both targets + +These are build artifacts, not authored source files. `SETTINGS.yaml` is the required runtime input. diff --git a/spec/team-protocol-v1.md b/spec/team-protocol-v1.md new file mode 100644 index 0000000..ef2c56a --- /dev/null +++ b/spec/team-protocol-v1.md @@ -0,0 +1,136 @@ +# Team Protocol v1 + +`TEAM.yaml` defines the team metadata and inventory protocol for portable generation targets in this repo. + +Implementation status: + +- Wave 1: protocol + documentation introduced +- Wave 2: generator + install integration completed; TEAM metadata is the active source of truth for team inventory behavior + +## Goals + +- Define a neutral, schema-backed source for agents, skills, and rules metadata. +- Keep Claude and Codex as adapter targets rather than protocol sources. +- Preserve Markdown as the human-authored instruction content format. +- Preserve current generated output behavior unless a narrow caveat is explicitly documented. + +## Scope + +Version 1 standardizes: + +- agent inventory and metadata required for generation +- skill inventory metadata +- rule inventory and deterministic ordering +- adapter boundaries for Claude and Codex +- validation requirements needed by the generator + +Version 1 does not standardize: + +- full prose structure for skills/rules/agents +- provider-specific runtime/tool grammars +- every future adapter target + +## Source-of-Truth Split + +- `SETTINGS.yaml`: runtime policy protocol (filesystem, approval intent, network, model intent) +- `TEAM.yaml`: team inventory protocol (agents, skills, rules metadata and references) +- Markdown files: instruction bodies + - agents: `agents/*.md` + - skills: `skills/*/SKILL.md` + - rules: `rules/*.md` + +Generated artifacts remain: + +- `settings.json` +- `claude/` +- `codex/` + +## Required TEAM Inventories + +`TEAM.yaml` must contain: + +- `agents` +- `skills` +- `rules` + +## Agent Contract + +Each agent entry includes metadata required for adapter generation: + +- `id` +- `name` +- `description` +- `model` +- `effort` +- `permission_mode` +- `tools` +- `disallowed_tools` +- `max_turns` +- `skills` +- optional `background` +- optional `memory` +- optional `isolation` +- `instruction_file` + +`instruction_file` points to the Markdown source for long-form instructions. + +## Skill Contract + +Each skill entry includes lightweight metadata and content reference: + +- `id` +- `description` +- `instruction_file` +- optional target/install metadata + +Skill prose remains in `skills/*/SKILL.md`. + +## Rule Contract + +Each rule entry includes: + +- `id` +- `source_file` +- deterministic order metadata +- optional target metadata + +Rule prose remains in `rules/*.md`. + +## Adapter Boundaries + +Claude and Codex are render targets. + +Current target behavior: + +- Claude generation consumes TEAM metadata + Markdown content and outputs: + - `claude/CLAUDE.md` + - `claude/settings.json` + - `claude/agents/*.md` +- Codex generation consumes TEAM metadata + Markdown content and outputs: + - `codex/config.toml` + - `codex/AGENTS.md` + - `codex/agents/*.toml` + +## Validation Requirements + +TEAM validation enforces schema + runtime checks for: + +- schema version correctness +- required sections present +- unique IDs for agents/skills/rules +- referenced files exist +- deterministic rule ordering inputs are valid +- `order` IDs match declared inventory keys +- item `id` matches keyed map entry + +## Compatibility Caveats + +- Existing YAML frontmatter in `agents/*.md` may remain for editorial continuity, but generation does not use it for team metadata. +- Output diffs that are purely formatting-related are acceptable; semantic behavior changes are not unless explicitly documented. +- TEAM schema is intentionally rigid/repo-specific in v1; inventory additions/removals require schema updates in lockstep. + +## Out of Scope + +- Rewriting instruction prose for style +- Full content schemas for skill/rule prose +- Generalizing all future adapters in v1 From 511076e0597490256f772e60db622cfd001f5002 Mon Sep 17 00:00:00 2001 From: Bryan Ramos <bryan@ramos.codes> Date: Thu, 2 Apr 2026 13:25:34 -0400 Subject: [PATCH 31/36] refactor(build): drive generation from shared configs --- .gitignore | 2 + generate.sh | 492 ++++++++++++++++++++++++++++++++++++++++++++------ install.sh | 76 +++++++- settings.json | 54 ------ 4 files changed, 507 insertions(+), 117 deletions(-) delete mode 100644 settings.json diff --git a/.gitignore b/.gitignore index 466ff5b..72c3fc4 100644 --- a/.gitignore +++ b/.gitignore @@ -10,7 +10,9 @@ settings.local.json Thumbs.db # Generated output (derived from source templates via generate.sh) +settings.json claude/ codex/ .claude .codex +.direnv diff --git a/generate.sh b/generate.sh index 60e28e3..abd2123 100755 --- a/generate.sh +++ b/generate.sh @@ -2,8 +2,9 @@ set -euo pipefail # generate.sh — generates both Claude and Codex output directories from -# shared agent source files. Agent source files (agents/*.md) are the -# single source of truth; this script derives tool-specific equivalents. +# shared agent source files plus a vendor-neutral runtime config. +# Agent source files (agents/*.md) are the single source of truth; this +# script derives tool-specific equivalents. # # Template variables in agent bodies are expanded per-target: # ${PLANS_DIR} — where plans live (.claude/plans vs plans) @@ -17,6 +18,8 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" AGENTS_SRC="$SCRIPT_DIR/agents" RULES_DIR="$SCRIPT_DIR/rules" CLAUDE_MD="$SCRIPT_DIR/CLAUDE.md" +SETTINGS_SHARED_YAML="$SCRIPT_DIR/SETTINGS.yaml" +TEAM_YAML="$SCRIPT_DIR/TEAM.yaml" SETTINGS_JSON="$SCRIPT_DIR/settings.json" CLAUDE_DIR="$SCRIPT_DIR/claude" @@ -48,14 +51,6 @@ extract_body() { awk 'BEGIN{fm=0} /^---$/{if(fm==0){fm=1;next} if(fm==1){fm=2;next}} fm==2{print}' "$file" } -# --------------------------------------------------------------------------- -# extract_frontmatter_block — extracts the raw frontmatter including delimiters -# --------------------------------------------------------------------------- -extract_frontmatter_block() { - local file="$1" - awk 'BEGIN{fm=0} /^---$/{if(fm==0){fm=1;print;next} if(fm==1){print;exit}} fm==1{print}' "$file" -} - # --------------------------------------------------------------------------- # expand_body — runs envsubst on body text, substituting only our 3 variables # $1 = body text @@ -75,6 +70,301 @@ expand_body() { done } +# --------------------------------------------------------------------------- +# yaml_escape_single_quoted — escapes text for YAML single-quoted scalars +# --------------------------------------------------------------------------- +yaml_escape_single_quoted() { + printf '%s' "$1" | sed "s/'/''/g" +} + +# --------------------------------------------------------------------------- +# csv_from_yaml_array — joins YAML array values from stdin with ", " +# --------------------------------------------------------------------------- +csv_from_yaml_array() { + local first=1 + local item + while IFS= read -r item; do + [ -n "$item" ] || continue + if [ "$first" -eq 0 ]; then + printf ', ' + fi + printf '%s' "$item" + first=0 + done +} + +# --------------------------------------------------------------------------- +# validate_team_protocol — validates TEAM protocol fields and referenced files +# --------------------------------------------------------------------------- +validate_team_protocol() { + [ -f "$TEAM_YAML" ] || { + echo "Error: missing $TEAM_YAML" + exit 1 + } + + yq -e '.version == 1' "$TEAM_YAML" > /dev/null + yq -e '.agents.order and .agents.items and .skills.order and .skills.items and .rules.order and .rules.items' "$TEAM_YAML" > /dev/null + + local section id ids_in_order + for section in agents skills rules; do + while IFS= read -r id; do + [ -n "$id" ] || continue + yq -e ".${section}.items.${id}" "$TEAM_YAML" > /dev/null + [ "$(yq -r ".${section}.items.${id}.id" "$TEAM_YAML")" = "$id" ] || { + echo "Error: TEAM ${section} item '${id}' has mismatched id field" + exit 1 + } + done < <(yq -r ".${section}.order[]" "$TEAM_YAML") + + ids_in_order="$(yq -r ".${section}.order[]" "$TEAM_YAML")" + while IFS= read -r id; do + [ -n "$id" ] || continue + printf '%s\n' "$ids_in_order" | grep -qx "$id" || { + echo "Error: TEAM ${section} item '${id}' missing from order list" + exit 1 + } + done < <(yq -r ".${section}.items | keys | .[]" "$TEAM_YAML") + done + + while IFS= read -r id; do + [ -n "$id" ] || continue + local path + path="$SCRIPT_DIR/$(yq -r ".agents.items.${id}.instruction_file" "$TEAM_YAML")" + [ -f "$path" ] || { + echo "Error: missing agent instruction file for '${id}': $path" + exit 1 + } + done < <(yq -r '.agents.order[]' "$TEAM_YAML") + + while IFS= read -r id; do + [ -n "$id" ] || continue + local path + path="$SCRIPT_DIR/$(yq -r ".skills.items.${id}.instruction_file" "$TEAM_YAML")" + [ -f "$path" ] || { + echo "Error: missing skill instruction file for '${id}': $path" + exit 1 + } + done < <(yq -r '.skills.order[]' "$TEAM_YAML") + + while IFS= read -r id; do + [ -n "$id" ] || continue + local path + path="$SCRIPT_DIR/$(yq -r ".rules.items.${id}.source_file" "$TEAM_YAML")" + [ -f "$path" ] || { + echo "Error: missing rule source file for '${id}': $path" + exit 1 + } + done < <(yq -r '.rules.order[]' "$TEAM_YAML") +} + +# --------------------------------------------------------------------------- +# validate_shared_settings — validates the shared protocol fields we rely on +# --------------------------------------------------------------------------- +validate_shared_settings() { + [ -f "$SETTINGS_SHARED_YAML" ] || { + echo "Error: missing $SETTINGS_SHARED_YAML" + exit 1 + } + + yq -e '.version == 1' "$SETTINGS_SHARED_YAML" > /dev/null + yq -e '.model.class == "fast" or .model.class == "balanced" or .model.class == "powerful"' "$SETTINGS_SHARED_YAML" > /dev/null + yq -e '.model.reasoning == "low" or .model.reasoning == "medium" or .model.reasoning == "high" or .model.reasoning == "max"' "$SETTINGS_SHARED_YAML" > /dev/null + yq -e '.runtime.filesystem == "read-only" or .runtime.filesystem == "workspace-write"' "$SETTINGS_SHARED_YAML" > /dev/null + yq -e '.runtime.approval == "manual" or .runtime.approval == "guarded-auto" or .runtime.approval == "full-auto"' "$SETTINGS_SHARED_YAML" > /dev/null + yq -e '(.runtime.network_access | type) == "!!bool"' "$SETTINGS_SHARED_YAML" > /dev/null + yq -e ' + (.runtime.tools // []) as $tools | + ( + $tools | + map( + select( + . == "shell" or + . == "read" or + . == "edit" or + . == "write" or + . == "glob" or + . == "grep" or + . == "web_fetch" or + . == "web_search" + ) + ) | + length + ) == ($tools | length) + ' "$SETTINGS_SHARED_YAML" > /dev/null +} + +# --------------------------------------------------------------------------- +# map_model_class_to_claude — maps shared model.class to Claude model value +# --------------------------------------------------------------------------- +map_model_class_to_claude() { + local model_class="$1" + case "$model_class" in + fast) echo "haiku" ;; + powerful) echo "opus" ;; + balanced) echo "sonnet" ;; + *) echo "sonnet" ;; + esac +} + +# --------------------------------------------------------------------------- +# map_approval_intent_to_codex_policy — shared approval intent to Codex value +# --------------------------------------------------------------------------- +map_approval_intent_to_codex_policy() { + local approval_intent="$1" + case "$approval_intent" in + manual) echo "on-request" ;; + full-auto) echo "never" ;; + guarded-auto) echo "untrusted" ;; + *) echo "untrusted" ;; + esac +} + +# --------------------------------------------------------------------------- +# map_filesystem_intent_to_claude_mode — shared filesystem to Claude mode +# --------------------------------------------------------------------------- +map_filesystem_intent_to_claude_mode() { + local filesystem="$1" + case "$filesystem" in + read-only) echo "plan" ;; + workspace-write) echo "acceptEdits" ;; + *) echo "acceptEdits" ;; + esac +} + +# --------------------------------------------------------------------------- +# map_portable_tool_to_claude — shared runtime tool to Claude allow-list name +# --------------------------------------------------------------------------- +map_portable_tool_to_claude() { + local tool="$1" + case "$tool" in + shell) echo "Bash" ;; + read) echo "Read" ;; + edit) echo "Edit" ;; + write) echo "Write" ;; + glob) echo "Glob" ;; + grep) echo "Grep" ;; + web_fetch) echo "WebFetch" ;; + web_search) echo "WebSearch" ;; + *) echo "$tool" ;; + esac +} + +# --------------------------------------------------------------------------- +# json_escape — escapes a string for JSON string literal output +# --------------------------------------------------------------------------- +json_escape() { + printf '%s' "$1" | sed 's/\\/\\\\/g; s/"/\\"/g' +} + +# --------------------------------------------------------------------------- +# json_array_from_lines — renders stdin as a compact JSON string array +# --------------------------------------------------------------------------- +json_array_from_lines() { + local first=1 + local item + + printf '[' + while IFS= read -r item; do + [ -n "$item" ] || continue + if [ "$first" -eq 0 ]; then + printf ', ' + fi + printf '"%s"' "$(json_escape "$item")" + first=0 + done + printf ']' +} + +# --------------------------------------------------------------------------- +# generate_legacy_settings_json — emits Claude-compatible settings.json +# from SETTINGS.yaml so downstream generation stays backward-compatible +# --------------------------------------------------------------------------- +generate_legacy_settings_json() { + local model_class model_reasoning runtime_filesystem runtime_approval + local claude_model claude_default_mode codex_approval_policy codex_network_access + local allow_json deny_json ask_json claude_md_excludes_json + + model_class="$(yq -r '.model.class' "$SETTINGS_SHARED_YAML")" + model_reasoning="$(yq -r '.model.reasoning' "$SETTINGS_SHARED_YAML")" + runtime_filesystem="$(yq -r '.runtime.filesystem' "$SETTINGS_SHARED_YAML")" + runtime_approval="$(yq -r '.runtime.approval' "$SETTINGS_SHARED_YAML")" + + claude_model="$(map_model_class_to_claude "$model_class")" + claude_default_mode="$(map_filesystem_intent_to_claude_mode "$runtime_filesystem")" + codex_approval_policy="$(yq -r '.targets.codex.approval_policy // ""' "$SETTINGS_SHARED_YAML")" + codex_network_access="$(yq -r '.targets.codex.network_access // .runtime.network_access // false' "$SETTINGS_SHARED_YAML")" + + if [ -z "$codex_approval_policy" ] || [ "$codex_approval_policy" = "null" ]; then + codex_approval_policy="$(map_approval_intent_to_codex_policy "$runtime_approval")" + fi + + allow_json="$( + yq -r '.runtime.tools[]' "$SETTINGS_SHARED_YAML" \ + | while IFS= read -r tool; do + map_portable_tool_to_claude "$tool" + done \ + | json_array_from_lines + )" + + deny_json="$( + { + yq -r '.safety.protected_paths[]' "$SETTINGS_SHARED_YAML" | while IFS= read -r path; do + printf 'Read(%s)\n' "$path" + printf 'Write(%s)\n' "$path" + printf 'Edit(%s)\n' "$path" + done + } | json_array_from_lines + )" + + ask_json="$( + yq -r '.safety.dangerous_shell_commands.ask[]' "$SETTINGS_SHARED_YAML" \ + | while IFS= read -r cmd; do + printf 'Bash(%s)\n' "$cmd" + done \ + | json_array_from_lines + )" + + claude_md_excludes_json="$( + yq -r '(.targets.claude.claude_md_excludes // [".claude/agent-memory/**"])[]' "$SETTINGS_SHARED_YAML" \ + | json_array_from_lines + )" + + cat > "$SETTINGS_JSON" <<JSON +{ + "\$schema": "https://json.schemastore.org/claude-code-settings.json", + "attribution": { + "commit": "", + "pr": "" + }, + "permissions": { + "allow": ${allow_json}, + "deny": ${deny_json}, + "ask": ${ask_json}, + "defaultMode": "${claude_default_mode}" + }, + "model": "${claude_model}", + "effortLevel": "${model_reasoning}", + "codex": { + "approvalPolicy": "${codex_approval_policy}", + "networkAccess": ${codex_network_access} + }, + "claudeMdExcludes": ${claude_md_excludes_json} +} +JSON +} + +# --------------------------------------------------------------------------- +# prepare_settings_json — ensures the Claude-compatible settings.json +# artifact exists from the shared runtime config +# --------------------------------------------------------------------------- +prepare_settings_json() { + echo "Using shared config: $SETTINGS_SHARED_YAML" + validate_shared_settings + validate_team_protocol + generate_legacy_settings_json + echo "Generated compatibility artifact: $SETTINGS_JSON" +} + # --------------------------------------------------------------------------- # map_model — maps Claude model name to Codex model name # --------------------------------------------------------------------------- @@ -103,7 +393,7 @@ map_effort() { } # --------------------------------------------------------------------------- -# map_sandbox_mode — determines Codex sandbox_mode from agent frontmatter +# map_sandbox_mode — determines Codex sandbox_mode from agent metadata # $1 = permissionMode value (plan / acceptEdits / "") # $2 = tools list (comma-separated) # --------------------------------------------------------------------------- @@ -129,6 +419,44 @@ map_sandbox_mode() { echo "read-only" } +# --------------------------------------------------------------------------- +# map_default_sandbox_mode — determines Codex sandbox_mode from shared config +# $1 = Claude permissions.defaultMode value +# --------------------------------------------------------------------------- +map_default_sandbox_mode() { + local default_mode="$1" + + case "$default_mode" in + plan) echo "read-only" ;; + acceptEdits) echo "workspace-write" ;; + *) echo "workspace-write" ;; + esac +} + +# --------------------------------------------------------------------------- +# map_approval_policy — determines Codex approval_policy from shared config +# $1 = Claude permissions.defaultMode value +# $2 = optional Codex approval override from shared config +# --------------------------------------------------------------------------- +map_approval_policy() { + local default_mode="$1" + local override="$2" + + if [ -n "$override" ] && [ "$override" != "null" ]; then + echo "$override" + return + fi + + # Closest intent mapping: + # - plan remains guarded + # - acceptEdits becomes Codex's closest "edit without constant prompts" mode + case "$default_mode" in + plan) echo "on-request" ;; + acceptEdits) echo "untrusted" ;; + *) echo "untrusted" ;; + esac +} + # --------------------------------------------------------------------------- # generate_claude — produces claude/ output directory # --------------------------------------------------------------------------- @@ -154,28 +482,72 @@ generate_claude() { ln -s ../skills "$CLAUDE_DIR/skills" echo "Symlinked: $CLAUDE_DIR/skills -> ../skills" - # Generate agent .md files with expanded template variables - for agent_file in "$AGENTS_SRC"/*.md; do - [ -f "$agent_file" ] || continue + # Generate agent .md files from TEAM metadata + markdown instruction body + local agent_id + while IFS= read -r agent_id; do + [ -n "$agent_id" ] || continue - local agent_basename - agent_basename="$(basename "$agent_file")" - local dst_file="$CLAUDE_AGENTS_DIR/$agent_basename" + local name description model effort permission_mode + local src_file dst_file body expanded_body + local max_turns background memory isolation + local tools_csv disallowed_tools_csv - # Extract frontmatter and body separately - local frontmatter body expanded_body - frontmatter="$(extract_frontmatter_block "$agent_file")" - body="$(extract_body "$agent_file")" + name="$(yq -r ".agents.items.${agent_id}.name" "$TEAM_YAML")" + description="$(yq -r ".agents.items.${agent_id}.description" "$TEAM_YAML")" + model="$(yq -r ".agents.items.${agent_id}.model" "$TEAM_YAML")" + effort="$(yq -r ".agents.items.${agent_id}.effort // \"\"" "$TEAM_YAML")" + permission_mode="$(yq -r ".agents.items.${agent_id}.permission_mode // \"\"" "$TEAM_YAML")" + tools_csv="$(yq -r ".agents.items.${agent_id}.tools[]" "$TEAM_YAML" | csv_from_yaml_array)" + disallowed_tools_csv="$(yq -r ".agents.items.${agent_id}.disallowed_tools // [] | .[]" "$TEAM_YAML" | csv_from_yaml_array)" + max_turns="$(yq -r ".agents.items.${agent_id}.max_turns // \"\"" "$TEAM_YAML")" + background="$(yq -r ".agents.items.${agent_id}.background // \"\"" "$TEAM_YAML")" + memory="$(yq -r ".agents.items.${agent_id}.memory // \"\"" "$TEAM_YAML")" + isolation="$(yq -r ".agents.items.${agent_id}.isolation // \"\"" "$TEAM_YAML")" + + src_file="$SCRIPT_DIR/$(yq -r ".agents.items.${agent_id}.instruction_file" "$TEAM_YAML")" + dst_file="$CLAUDE_AGENTS_DIR/${name}.md" + + body="$(extract_body "$src_file")" expanded_body="$(expand_body "$body" "${CLAUDE_VARS[@]}")" - # Reassemble: frontmatter + expanded body { - echo "$frontmatter" + echo "---" + echo "name: '$(yaml_escape_single_quoted "$name")'" + echo "description: '$(yaml_escape_single_quoted "$description")'" + echo "model: '$(yaml_escape_single_quoted "$model")'" + if [ -n "$effort" ] && [ "$effort" != "null" ]; then + echo "effort: '$(yaml_escape_single_quoted "$effort")'" + fi + if [ -n "$permission_mode" ] && [ "$permission_mode" != "null" ]; then + echo "permissionMode: '$(yaml_escape_single_quoted "$permission_mode")'" + fi + echo "tools: '$(yaml_escape_single_quoted "$tools_csv")'" + if [ -n "$disallowed_tools_csv" ] && [ "$disallowed_tools_csv" != "null" ]; then + echo "disallowedTools: '$(yaml_escape_single_quoted "$disallowed_tools_csv")'" + fi + if [ "$background" = "true" ]; then + echo "background: true" + fi + if [ -n "$memory" ] && [ "$memory" != "null" ]; then + echo "memory: '$(yaml_escape_single_quoted "$memory")'" + fi + if [ -n "$isolation" ] && [ "$isolation" != "null" ]; then + echo "isolation: '$(yaml_escape_single_quoted "$isolation")'" + fi + if [ -n "$max_turns" ] && [ "$max_turns" != "null" ]; then + echo "maxTurns: $max_turns" + fi + echo "skills:" + yq -r ".agents.items.${agent_id}.skills[]" "$TEAM_YAML" | while IFS= read -r skill; do + echo " - $(yaml_escape_single_quoted "$skill")" + done + echo "---" + echo "" echo "$expanded_body" } > "$dst_file" echo "Generated: $dst_file" - done + done < <(yq -r '.agents.order[]' "$TEAM_YAML") } # --------------------------------------------------------------------------- @@ -189,28 +561,23 @@ generate_codex() { rm -rf "$CODEX_DIR" mkdir -p "$CODEX_AGENTS_DIR" - # Generate agent .toml files + # Generate agent .toml files from TEAM metadata + markdown instruction body echo "Generating Codex agent definitions..." - for agent_file in "$AGENTS_SRC"/*.md; do - [ -f "$agent_file" ] || continue + local agent_id + while IFS= read -r agent_id; do + [ -n "$agent_id" ] || continue - local agent_basename - agent_basename="$(basename "$agent_file" .md)" - local dst_file="$CODEX_AGENTS_DIR/${agent_basename}.toml" - - # Extract YAML frontmatter using yq - local frontmatter - frontmatter="$(yq --front-matter=extract '.' "$agent_file")" - - # Extract individual fields from frontmatter local name description model effort permission_mode tools disallowed_tools - name="$(echo "$frontmatter" | yq '.name // ""')" - description="$(echo "$frontmatter" | yq '.description // ""')" - model="$(echo "$frontmatter" | yq '.model // ""')" - effort="$(echo "$frontmatter" | yq '.effort // ""')" - permission_mode="$(echo "$frontmatter" | yq '.permissionMode // ""')" - tools="$(echo "$frontmatter" | yq '.tools // ""')" - disallowed_tools="$(echo "$frontmatter" | yq '.disallowedTools // ""')" + local src_file dst_file + name="$(yq -r ".agents.items.${agent_id}.name" "$TEAM_YAML")" + description="$(yq -r ".agents.items.${agent_id}.description" "$TEAM_YAML")" + model="$(yq -r ".agents.items.${agent_id}.model" "$TEAM_YAML")" + effort="$(yq -r ".agents.items.${agent_id}.effort // \"\"" "$TEAM_YAML")" + permission_mode="$(yq -r ".agents.items.${agent_id}.permission_mode // \"\"" "$TEAM_YAML")" + tools="$(yq -r ".agents.items.${agent_id}.tools[]" "$TEAM_YAML" | csv_from_yaml_array)" + disallowed_tools="$(yq -r ".agents.items.${agent_id}.disallowed_tools // [] | .[]" "$TEAM_YAML" | csv_from_yaml_array)" + src_file="$SCRIPT_DIR/$(yq -r ".agents.items.${agent_id}.instruction_file" "$TEAM_YAML")" + dst_file="$CODEX_AGENTS_DIR/${name}.toml" # Map to Codex equivalents local codex_model codex_effort codex_sandbox @@ -220,7 +587,7 @@ generate_codex() { # Extract and expand body with Codex variable values local body expanded_body - body="$(extract_body "$agent_file")" + body="$(extract_body "$src_file")" expanded_body="$(expand_body "$body" "${CODEX_VARS[@]}")" # Build developer_instructions: append disallowedTools note if present @@ -245,42 +612,48 @@ ${developer_instructions} TOML echo "Generated: $dst_file" - done + done < <(yq -r '.agents.order[]' "$TEAM_YAML") - # Generate AGENTS.md — concatenate rules/*.md with tool-agnostic header + # Generate AGENTS.md — concatenate TEAM-ordered rules with tool-agnostic header echo "" echo "Generating codex/AGENTS.md..." { echo "# Agent Team Instructions" echo "" echo "Agent-team specific protocols live in skills (orchestrate, conventions, worker-protocol, qa-checklist, message-schema, project)." - for rules_file in "$RULES_DIR"/*.md; do + local rule_id rules_file + while IFS= read -r rule_id; do + [ -n "$rule_id" ] || continue + yq -r ".rules.items.${rule_id}.applies_to[]" "$TEAM_YAML" | grep -qx "codex" || continue + rules_file="$SCRIPT_DIR/$(yq -r ".rules.items.${rule_id}.source_file" "$TEAM_YAML")" echo "" cat "$rules_file" - done + done < <(yq -r '.rules.order[]' "$TEAM_YAML") } > "$CODEX_DIR/AGENTS.md" echo "Generated: $CODEX_DIR/AGENTS.md" - # Generate config.toml — derive sandbox_mode from settings.json defaultMode + # Generate config.toml — derive sandbox/approval defaults from shared config echo "" echo "Generating codex/config.toml..." - local default_mode - default_mode="$(yq -r '.permissions.defaultMode // "acceptEdits"' "$SETTINGS_JSON")" + local default_mode codex_approval_override codex_network_access + default_mode="$(map_filesystem_intent_to_claude_mode "$(yq -r '.runtime.filesystem' "$SETTINGS_SHARED_YAML")")" + codex_approval_override="$(yq -r '.targets.codex.approval_policy // ""' "$SETTINGS_SHARED_YAML")" + codex_network_access="$(yq -r '.targets.codex.network_access // .runtime.network_access // false' "$SETTINGS_SHARED_YAML")" - # Map Claude defaultMode to Codex sandbox_mode - local config_sandbox - case "$default_mode" in - plan) config_sandbox="read-only" ;; - acceptEdits) config_sandbox="workspace-write" ;; - *) config_sandbox="workspace-write" ;; - esac + local config_sandbox config_approval + config_sandbox="$(map_default_sandbox_mode "$default_mode")" + config_approval="$(map_approval_policy "$default_mode" "$codex_approval_override")" cat > "$CODEX_DIR/config.toml" <<TOML +#:schema https://developers.openai.com/codex/config-schema.json model = "gpt-5.3-codex" model_reasoning_effort = "medium" sandbox_mode = "${config_sandbox}" -approval_policy = "on-request" +approval_policy = "${config_approval}" + +[sandbox_workspace_write] +network_access = ${codex_network_access} TOML echo "Generated: $CODEX_DIR/config.toml" } @@ -288,6 +661,7 @@ TOML # --------------------------------------------------------------------------- # Main # --------------------------------------------------------------------------- +prepare_settings_json generate_claude generate_codex diff --git a/install.sh b/install.sh index b3be864..2eb0475 100755 --- a/install.sh +++ b/install.sh @@ -9,6 +9,7 @@ CLAUDE_DIR="$HOME/.claude" AGENTS_SRC="$SCRIPT_DIR/claude/agents" SKILLS_SRC="$SCRIPT_DIR/skills" RULES_SRC="$SCRIPT_DIR/rules" +TEAM_YAML="$SCRIPT_DIR/TEAM.yaml" AGENTS_DST="$CLAUDE_DIR/agents" SKILLS_DST="$CLAUDE_DIR/skills" RULES_DST="$CLAUDE_DIR/rules" @@ -122,6 +123,48 @@ create_file_symlink() { echo "Linked: $dst -> $src" } +# Return one skill id per line for a target platform from TEAM.yaml. +# Falls back to empty output when TEAM.yaml is unavailable. +list_team_skills_for_target() { + local target="$1" + + if [ ! -f "$TEAM_YAML" ]; then + return 0 + fi + + # Validate TEAM parseability before resolving inventory. + yq -e '.version == 1 and has("skills") and (.skills | has("order")) and (.skills | has("items"))' "$TEAM_YAML" > /dev/null + + local skill_id applies + while IFS= read -r skill_id; do + [ -n "$skill_id" ] || continue + applies="$(yq -r ".skills.items.\"$skill_id\".applies_to[]? // \"\"" "$TEAM_YAML")" + if printf '%s\n' "$applies" | grep -Fxq "$target"; then + printf '%s\n' "$skill_id" + fi + done < <(yq -r '.skills.order[]' "$TEAM_YAML") +} + +# Resolve a TEAM skill id to its source directory using instruction_file. +resolve_skill_dir_from_team() { + local skill_id="$1" + + if [ ! -f "$TEAM_YAML" ]; then + return 1 + fi + + local instruction_file skill_dir + instruction_file="$( + yq -r ".skills.items.\"$skill_id\".instruction_file // \"\"" "$TEAM_YAML" + )" + [ -n "$instruction_file" ] || return 1 + + skill_dir="$(dirname "$SCRIPT_DIR/$instruction_file")" + [ -d "$skill_dir" ] || return 1 + + printf '%s\n' "$skill_dir" +} + create_symlink "$AGENTS_SRC" "$AGENTS_DST" "agents" create_symlink "$SKILLS_SRC" "$SKILLS_DST" "skills" create_symlink "$RULES_SRC" "$RULES_DST" "rules" @@ -139,10 +182,35 @@ if [ -d "$SCRIPT_DIR/codex" ]; then # Skills: symlink each skill directory into ~/.codex/skills/ # (Can't replace the whole directory — .system/ must remain intact) mkdir -p "$CODEX_DIR/skills" - for skill_dir in "$SKILLS_SRC"/*/; do - skill_name="$(basename "$skill_dir")" - create_symlink "$skill_dir" "$CODEX_DIR/skills/$skill_name" "codex skill: $skill_name" - done + if [ -f "$TEAM_YAML" ]; then + team_codex_skills_tmp="$(mktemp)" + if ! list_team_skills_for_target "codex" > "$team_codex_skills_tmp" 2>/dev/null; then + echo "Warning: TEAM.yaml exists but could not be parsed; falling back to directory-based Codex skill install." + for skill_dir in "$SKILLS_SRC"/*/; do + skill_name="$(basename "$skill_dir")" + create_symlink "$skill_dir" "$CODEX_DIR/skills/$skill_name" "codex skill: $skill_name" + done + rm -f "$team_codex_skills_tmp" + else + # TEAM is authoritative when present, including the explicit zero-skills case. + while IFS= read -r skill_id; do + [ -n "$skill_id" ] || continue + skill_dir="$(resolve_skill_dir_from_team "$skill_id" || true)" + if [ -z "$skill_dir" ]; then + echo "Warning: TEAM.yaml skill '$skill_id' has no valid instruction_file directory; skipping." + continue + fi + create_symlink "$skill_dir" "$CODEX_DIR/skills/$skill_id" "codex skill: $skill_id" + done < "$team_codex_skills_tmp" + rm -f "$team_codex_skills_tmp" + fi + else + # Legacy fallback only when TEAM.yaml is absent. + for skill_dir in "$SKILLS_SRC"/*/; do + skill_name="$(basename "$skill_dir")" + create_symlink "$skill_dir" "$CODEX_DIR/skills/$skill_name" "codex skill: $skill_name" + done + fi # Generated agents if [ -d "$SCRIPT_DIR/codex/agents" ]; then diff --git a/settings.json b/settings.json deleted file mode 100644 index 582b9a2..0000000 --- a/settings.json +++ /dev/null @@ -1,54 +0,0 @@ -{ - "$schema": "https://json.schemastore.org/claude-code-settings.json", - "attribution": { - "commit": "", - "pr": "" - }, - "permissions": { - "allow": [ - "Bash", - "Read", - "Edit", - "Write", - "Glob", - "Grep", - "WebFetch", - "WebSearch" - ], - "deny": [ - "Read(~/.ssh/**)", - "Read(~/.aws/**)", - "Read(~/.gnupg/**)", - "Read(**/.env*)", - "Write(~/.ssh/**)", - "Write(~/.aws/**)", - "Write(~/.gnupg/**)", - "Write(**/.env*)", - "Edit(~/.ssh/**)", - "Edit(~/.aws/**)", - "Edit(~/.gnupg/**)", - "Edit(**/.env*)" - ], - "ask": [ - "Bash(rm *)", - "Bash(rmdir *)", - "Bash(git push --force*)", - "Bash(git push -f*)", - "Bash(git reset --hard*)", - "Bash(git clean *)", - "Bash(chmod *)", - "Bash(dd *)", - "Bash(mkfs*)", - "Bash(shred *)", - "Bash(kill *)", - "Bash(killall *)", - "Bash(sudo *)" - ], - "defaultMode": "acceptEdits" - }, - "model": "sonnet", - "effortLevel": "medium", - "claudeMdExcludes": [ - ".claude/agent-memory/**" - ] -} From eda47480123d3bfd710a8a754347ae1f9df96bcf Mon Sep 17 00:00:00 2001 From: Bryan Ramos <bryan@ramos.codes> Date: Thu, 2 Apr 2026 13:26:08 -0400 Subject: [PATCH 32/36] feat(workflow): add flake and just entrypoints --- CLAUDE.md | 2 +- README.md | 149 ++++++++++++++++++++++++++++++++++++++++----- flake.nix | 176 +++++++++++++++++++++++++++++++++++++++++++++++++++--- justfile | 21 +++++++ 4 files changed, 323 insertions(+), 25 deletions(-) create mode 100644 justfile diff --git a/CLAUDE.md b/CLAUDE.md index 1e2879f..cf054e5 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,4 +1,4 @@ # Global Claude Code Instructions -Rules are modularized in `~/.claude/rules/` and loaded automatically. +Rules are modularized in `rules/` and loaded automatically by the generated Claude config. Agent-team specific protocols live in skills (orchestrate, conventions, worker-protocol, qa-checklist, message-schema, project). diff --git a/README.md b/README.md index c56b495..06a9e4f 100644 --- a/README.md +++ b/README.md @@ -1,22 +1,46 @@ # agent-team -A portable agent team configuration for Claude Code and Codex CLI. Clone it, run `./generate.sh` + `./install.sh`, and both tools get a full team of specialized subagents and shared skills — on any machine. +A portable agent team configuration for Claude Code and Codex CLI. Clone it, run the flake entrypoints or the `just` wrapper, and both tools get a full team of specialized subagents and shared skills on any machine. ## Quick install ```bash -git clone <repo-url> ~/agent-team -cd ~/agent-team +git clone <repo-url> +cd agent-team nix develop # enter devShell with yq + envsubst -./generate.sh # generate Claude + Codex config from templates -./install.sh # symlinks into ~/.claude/ and ~/.codex/ (if present) +nix run .#check # validate protocols + generate artifacts +nix run .#install # install into your Claude/Codex config dirs ``` -`generate.sh` expands shared agent templates into tool-specific outputs for Claude Code and Codex CLI. `install.sh` symlinks the results into `~/.claude/` and `~/.codex/`. Works on Linux, macOS, and Windows (Git Bash). +The supported user-facing entrypoints are the flake apps and the `just` wrapper. `generate.sh` and `install.sh` remain the internal implementation layer behind them. Works on Linux, macOS, and Windows (Git Bash). + +## Nix entrypoints + +The flake exposes formal workflow entrypoints: + +```bash +nix run .#validate # syntax + protocol presence/basic shape checks +nix run .#build # generate settings.json + claude/ + codex/ +nix run .#check # validate + build +nix run .#install # run install.sh +nix flake check # run flake checks (validate + build in sandboxed check derivations) +``` + +`just` is also supported as a convenience wrapper over those same flake commands: + +```bash +just validate +just build +just check +just install +just clean # removes generated artifacts: settings.json + claude/ + codex/ +``` + +`generate.sh` and `install.sh` are kept as internal implementation details for portability and debugging, but they are no longer the primary documented workflow. ## Maintenance -**Symlink fragility:** `~/.claude/CLAUDE.md` and `~/.claude/settings.json` are installed as symlinks by `install.sh`. Some tools (including Claude Code itself when writing settings) resolve symlinks to regular files on write, silently breaking the link. If edits to the repo are no longer reflected in `~/.claude/`, re-run `./install.sh` to restore the symlinks. +**Symlink fragility:** the generated Claude files are installed as symlinks by `install.sh`. Some tools (including Claude Code itself when writing settings) resolve symlinks to regular files on write, silently breaking the link. If edits to the repo are no longer reflected in your Claude config dir, re-run `./install.sh` to restore the symlinks. ## Agents @@ -43,7 +67,7 @@ nix develop # enter devShell with yq + envsubst ## Rules -Global instructions are modularized in `rules/` and auto-loaded by Claude Code from `~/.claude/rules/` on every session. Each file covers a focused topic (git workflow, Nix preferences, response style, etc.). Agent-team specific protocols live in skills, not rules. +Global instructions are modularized in `rules/` and auto-loaded by Claude Code from the installed Claude config on every session. Each file covers a focused topic (git workflow, Nix preferences, response style, etc.). Agent-team specific protocols live in skills, not rules. ## How to use @@ -65,7 +89,7 @@ For simple tasks, invoke an agent directly: ### Codex CLI -Agents are available as named agents in `~/.codex/agents/`. Invoke them with: +Agents are available as named agents in the installed Codex config. Invoke them with: ``` codex --agent worker "Fix the broken pagination in the user list endpoint" @@ -73,17 +97,112 @@ codex --agent worker "Fix the broken pagination in the user list endpoint" ## Dual-target generation -Agent source files in `agents/` are the single source of truth. `generate.sh` derives tool-specific outputs for both Claude Code and [OpenAI Codex CLI](https://github.com/openai/codex). +This repo uses two authored protocol files: + +- [SETTINGS.yaml](SETTINGS.yaml) for runtime policy (filesystem, approvals, network, model intent) +- [TEAM.yaml](TEAM.yaml) for team inventory metadata (agents, skills, rules) + +Long-form instructions remain authored in Markdown (`agents/*.md`, `skills/*/SKILL.md`, `rules/*.md`). + +Runtime policy is documented in [spec/agent-runtime-v1.md](spec/agent-runtime-v1.md) and described by [schemas/agent-runtime.schema.json](schemas/agent-runtime.schema.json). Team inventory is documented in [spec/team-protocol-v1.md](spec/team-protocol-v1.md). `generate.sh` derives tool-specific outputs for both Claude Code and [OpenAI Codex CLI](https://github.com/openai/codex). ### What gets generated | Source | Generated | Location | |---|---|---| -| `agents/*.md` (templates) | `claude/agents/*.md` | `~/.claude/agents/` | -| `agents/*.md` (templates) | `codex/agents/*.toml` | `~/.codex/agents/` | -| `rules/*.md` | `codex/AGENTS.md` | `~/.codex/AGENTS.md` | -| `settings.json` | `codex/config.toml` | `~/.codex/config.toml` | -| `skills/` | (shared as-is) | `~/.claude/skills/` + `~/.agents/skills/` | +| `TEAM.yaml` + `agents/*.md` | `claude/agents/*.md` | Claude config dir | +| `TEAM.yaml` + `agents/*.md` | `codex/agents/*.toml` | Codex config dir | +| `SETTINGS.yaml` | `settings.json` (compatibility artifact, generated) | repo root | +| `SETTINGS.yaml` | `claude/settings.json` | Claude config dir | +| `SETTINGS.yaml` | `codex/config.toml` | Codex config dir | +| `TEAM.yaml` + `rules/*.md` | `codex/AGENTS.md` | Codex config dir | +| `TEAM.yaml` + `skills/*/SKILL.md` | installed skill dirs | installed skill dirs | + +All final config files are generated artifacts. The authored protocol sources are `SETTINGS.yaml`, `TEAM.yaml`, and Markdown instruction content. The primary workflows are `nix run .#build` / `nix run .#install` or the equivalent `just` commands. + +Narrow compatibility caveats: + +- TEAM schema is intentionally rigid/repo-specific in v1. Inventory changes require schema updates in lockstep. +- Claude generated agent frontmatter is normalized by generator serialization (field order/quoting), which may produce non-semantic diffs. +- Codex skill installation is TEAM-authoritative when `TEAM.yaml` is present. Legacy directory fallback is used only when TEAM is absent or unparseable. + +Shared runtime intent is generated conservatively across tools: + +| Shared source | Claude Code | Codex CLI | +|---|---|---| +| `runtime.filesystem = read-only` | `permissions.defaultMode = "plan"` | `sandbox_mode = "read-only"` | +| `runtime.filesystem = workspace-write` | `permissions.defaultMode = "acceptEdits"` | `sandbox_mode = "workspace-write"` | +| `runtime.approval = manual` | partially represented | `approval_policy = "on-request"` | +| `runtime.approval = guarded-auto` | partially represented | `approval_policy = "untrusted"` | +| `runtime.approval = full-auto` | partially represented | `approval_policy = "never"` | + +Codex does not support Claude's per-tool `allow` / `deny` / `ask` patterns directly. The shared protocol keeps the intent portable, then adapters derive the closest target behavior. Use target-specific fields only where there is no shared equivalent: + +```yaml +targets: + codex: + approval_policy: untrusted + network_access: false + claude: + claude_md_excludes: + - .claude/agent-memory/** +``` + +## Shared protocol + +The protocol source is YAML because it is easier to read and annotate than JSON or TOML while still being easy to validate with JSON Schema. + +- Runtime policy: [SETTINGS.yaml](SETTINGS.yaml) +- Runtime schema: [schemas/agent-runtime.schema.json](schemas/agent-runtime.schema.json) +- Runtime spec: [spec/agent-runtime-v1.md](spec/agent-runtime-v1.md) +- Team/inventory spec: [spec/team-protocol-v1.md](spec/team-protocol-v1.md) + +The protocol is intentionally small in v1: + +- portable model tier and reasoning level +- filesystem access intent +- approval intent +- network access +- portable tool classes +- protected paths +- dangerous shell command prompts +- limited target-specific escape hatches + +Example: + +```yaml +version: 1 + +model: + class: balanced + reasoning: medium + +runtime: + filesystem: workspace-write + approval: guarded-auto + network_access: false + tools: + - shell + - read + - edit + - write + - glob + - grep + - web_fetch + - web_search + +safety: + protected_paths: + - ~/.ssh/** + - ~/.aws/** + - ~/.gnupg/** + - "**/.env*" + dangerous_shell_commands: + ask: + - rm * + - git reset --hard* + - sudo * +``` ## Model mapping diff --git a/flake.nix b/flake.nix index 3d58609..3b96dd7 100644 --- a/flake.nix +++ b/flake.nix @@ -1,18 +1,176 @@ { inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable"; - outputs = { nixpkgs, ... }: + outputs = { self, nixpkgs, ... }: let systems = [ "x86_64-linux" "aarch64-linux" "x86_64-darwin" "aarch64-darwin" ]; forAllSystems = f: nixpkgs.lib.genAttrs systems (system: f nixpkgs.legacyPackages.${system}); - in { + in + { devShells = forAllSystems (pkgs: { - default = pkgs.mkShell { - packages = with pkgs; [ - yq-go - gettext - codex - ]; - }; + default = pkgs.mkShell { + packages = with pkgs; [ + yq-go + gettext + just + ]; + }; + }); + + apps = forAllSystems (pkgs: let + pythonEnv = pkgs.python3.withPackages (ps: with ps; [ pyyaml jsonschema ]); + runtimeInputs = with pkgs; [ + bash + yq-go + gettext + pythonEnv + ]; + bashBin = "${pkgs.bash}/bin/bash"; + + validateCmd = '' + # Script syntax checks + ${bashBin} -n ./generate.sh + ${bashBin} -n ./install.sh + + # Protocol file presence checks + test -f ./SETTINGS.yaml + test -f ./TEAM.yaml + test -f ./schemas/agent-runtime.schema.json + test -f ./schemas/team.schema.json + + # Basic protocol shape checks + yq -e '.version == 1' ./SETTINGS.yaml + yq -e '.version == 1' ./TEAM.yaml + yq -e '.agents.order | type == "!!seq"' ./TEAM.yaml + yq -e '.skills.order | type == "!!seq"' ./TEAM.yaml + yq -e '.rules.order | type == "!!seq"' ./TEAM.yaml + + # JSON Schema validation for protocol files + python <<'PY' + import json + from pathlib import Path + + import yaml + from jsonschema import validate + + root = Path(".") + settings_data = yaml.safe_load((root / "SETTINGS.yaml").read_text()) + team_data = yaml.safe_load((root / "TEAM.yaml").read_text()) + settings_schema = json.loads((root / "schemas/agent-runtime.schema.json").read_text()) + team_schema = json.loads((root / "schemas/team.schema.json").read_text()) + + validate(instance=settings_data, schema=settings_schema) + validate(instance=team_data, schema=team_schema) + PY + ''; + + mkAppScript = name: text: + pkgs.writeShellApplication { + inherit name runtimeInputs text; + }; + in { + build = { + type = "app"; + program = "${mkAppScript "build" '' + set -euo pipefail + test -f ./generate.sh || { echo "Run this command from the repository root."; exit 1; } + ${bashBin} ./generate.sh + ''}/bin/build"; + meta.description = "Generate Claude and Codex build artifacts from the authored protocol files."; + }; + + validate = { + type = "app"; + program = "${mkAppScript "validate" '' + set -euo pipefail + test -f ./generate.sh || { echo "Run this command from the repository root."; exit 1; } + ${validateCmd} + ''}/bin/validate"; + meta.description = "Validate scripts and protocol files."; + }; + + check = { + type = "app"; + program = "${mkAppScript "check" '' + set -euo pipefail + test -f ./generate.sh || { echo "Run this command from the repository root."; exit 1; } + ${validateCmd} + ${bashBin} ./generate.sh + ''}/bin/check"; + meta.description = "Run validation and generation together."; + }; + + install = { + type = "app"; + program = "${mkAppScript "install" '' + set -euo pipefail + test -f ./install.sh || { echo "Run this command from the repository root."; exit 1; } + ${bashBin} ./install.sh + ''}/bin/install"; + meta.description = "Install generated artifacts into Claude and Codex config directories."; + }; + }); + + checks = forAllSystems (pkgs: let + pythonEnv = pkgs.python3.withPackages (ps: with ps; [ pyyaml jsonschema ]); + runtimeInputs = with pkgs; [ + bash + yq-go + gettext + pythonEnv + ]; + bashBin = "${pkgs.bash}/bin/bash"; + + validateCmd = '' + ${bashBin} -n ./generate.sh + ${bashBin} -n ./install.sh + test -f ./SETTINGS.yaml + test -f ./TEAM.yaml + test -f ./schemas/agent-runtime.schema.json + test -f ./schemas/team.schema.json + yq -e '.version == 1' ./SETTINGS.yaml + yq -e '.version == 1' ./TEAM.yaml + yq -e '.agents.order | type == "!!seq"' ./TEAM.yaml + yq -e '.skills.order | type == "!!seq"' ./TEAM.yaml + yq -e '.rules.order | type == "!!seq"' ./TEAM.yaml + + python <<'PY' + import json + from pathlib import Path + + import yaml + from jsonschema import validate + + root = Path(".") + settings_data = yaml.safe_load((root / "SETTINGS.yaml").read_text()) + team_data = yaml.safe_load((root / "TEAM.yaml").read_text()) + settings_schema = json.loads((root / "schemas/agent-runtime.schema.json").read_text()) + team_schema = json.loads((root / "schemas/team.schema.json").read_text()) + + validate(instance=settings_data, schema=settings_schema) + validate(instance=team_data, schema=team_schema) + PY + ''; + + mkCheck = name: text: + pkgs.runCommand name { nativeBuildInputs = runtimeInputs; src = ./.; } '' + mkdir -p "$TMPDIR/repo" + cp -R "$src"/. "$TMPDIR/repo" + chmod -R u+w "$TMPDIR/repo" + cd "$TMPDIR/repo" + ${text} + touch "$out" + ''; + in { + validate = mkCheck "agent-team-validate-check" '' + set -euxo pipefail + ${validateCmd} + ''; + + build = mkCheck "agent-team-build-check" '' + set -euxo pipefail + ${validateCmd} + ${bashBin} ./generate.sh + ''; }); }; } diff --git a/justfile b/justfile new file mode 100644 index 0000000..756834e --- /dev/null +++ b/justfile @@ -0,0 +1,21 @@ +set shell := ["bash", "-eu", "-o", "pipefail", "-c"] + +default: help + +help: + @just --list + +validate: + nix run .#validate + +build: + nix run .#build + +check: + nix run .#check + +install: + nix run .#install + +clean: + rm -rf settings.json claude codex From 947886fba58f1855ff68565409708a347f1109e5 Mon Sep 17 00:00:00 2001 From: Bryan Ramos <bryan@ramos.codes> Date: Thu, 2 Apr 2026 13:28:10 -0400 Subject: [PATCH 33/36] fixed title --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 06a9e4f..bc27ea3 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# agent-team +# AI.conf A portable agent team configuration for Claude Code and Codex CLI. Clone it, run the flake entrypoints or the `just` wrapper, and both tools get a full team of specialized subagents and shared skills on any machine. From 2a2cd3ca2285f9cdb1ebe00f9fc7f2acbe723c2b Mon Sep 17 00:00:00 2001 From: Bryan Ramos <bryan@ramos.codes> Date: Thu, 2 Apr 2026 14:05:51 -0400 Subject: [PATCH 34/36] Refine agent escalation contracts --- README.md | 54 ++++++++++++++++++---------------- TEAM.yaml | 50 ++++++++++++++++++++++++++++++- agents/debugger.md | 2 +- agents/grunt.md | 37 +++++++++++++++++++++++ agents/senior.md | 38 ++++++++++++++++++++++++ agents/worker.md | 18 ++++++++++-- schemas/team.schema.json | 24 +++++++++++++++ skills/message-schema/SKILL.md | 13 ++++++++ skills/orchestrate/SKILL.md | 48 ++++++++++++++++++++---------- 9 files changed, 238 insertions(+), 46 deletions(-) create mode 100644 agents/grunt.md create mode 100644 agents/senior.md diff --git a/README.md b/README.md index bc27ea3..b780659 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # AI.conf -A portable agent team configuration for Claude Code and Codex CLI. Clone it, run the flake entrypoints or the `just` wrapper, and both tools get a full team of specialized subagents and shared skills on any machine. +A portable agent-team config repo with shared authored sources and generated target outputs. Clone it, run the flake entrypoints or the `just` wrapper, and the repo will generate/install the target-specific config for the supported tools. ## Quick install @@ -9,7 +9,7 @@ git clone <repo-url> cd agent-team nix develop # enter devShell with yq + envsubst nix run .#check # validate protocols + generate artifacts -nix run .#install # install into your Claude/Codex config dirs +nix run .#install # install generated outputs into the supported target config dirs ``` The supported user-facing entrypoints are the flake apps and the `just` wrapper. `generate.sh` and `install.sh` remain the internal implementation layer behind them. Works on Linux, macOS, and Windows (Git Bash). @@ -40,19 +40,21 @@ just clean # removes generated artifacts: settings.json + claude/ + cod ## Maintenance -**Symlink fragility:** the generated Claude files are installed as symlinks by `install.sh`. Some tools (including Claude Code itself when writing settings) resolve symlinks to regular files on write, silently breaking the link. If edits to the repo are no longer reflected in your Claude config dir, re-run `./install.sh` to restore the symlinks. +**Symlink fragility:** some generated target files are installed as symlinks by `install.sh`. Tools that rewrite those files may replace the symlink with a regular file. If repo edits stop being reflected in an installed target config, re-run `./install.sh` to restore the symlink. ## Agents -| Agent | Model | Role | +| Agent | Model policy | Role | |---|---|---| -| `worker` | sonnet (haiku/opus by orchestrator) | Universal implementer. Model scaled to task complexity. | -| `debugger` | sonnet | Diagnoses and fixes bugs with minimal targeted changes. | -| `documenter` | sonnet | Writes and updates docs. Never modifies source code. | -| `architect` | opus | Triage, research coordination, architecture design, wave decomposition. Read-only. | -| `researcher` | sonnet | Parallel fact-finding. One instance per research question. Read-only. | -| `reviewer` | sonnet | Code quality review + AC verification + claim checking. Read-only. | -| `auditor` | sonnet | Security analysis + runtime validation. Read-only, runs in background. | +| `grunt` | fast | Cheap implementer for trivial, tightly scoped work. | +| `worker` | balanced | Standard implementer for normal development tasks. | +| `senior` | strong | Expensive implementer for ambiguous, architectural, or high-risk work. | +| `debugger` | balanced | Diagnoses and fixes bugs with minimal targeted changes. | +| `documenter` | balanced | Writes and updates docs. Never modifies source code. | +| `architect` | strong | Triage, research coordination, architecture design, wave decomposition. Read-only. | +| `researcher` | balanced | Parallel fact-finding. One instance per research question. Read-only. | +| `reviewer` | balanced | Code quality review + AC verification + claim checking. Read-only. | +| `auditor` | balanced | Security analysis + runtime validation. Read-only, runs in background. | ## Skills @@ -67,9 +69,9 @@ just clean # removes generated artifacts: settings.json + claude/ + cod ## Rules -Global instructions are modularized in `rules/` and auto-loaded by Claude Code from the installed Claude config on every session. Each file covers a focused topic (git workflow, Nix preferences, response style, etc.). Agent-team specific protocols live in skills, not rules. +Global instructions are modularized in `rules/`. Each file covers a focused topic (git workflow, Nix preferences, response style, etc.). Agent-team specific protocols live in skills, not rules. Target adapters decide how those rules are surfaced. -## How to use +## Target usage ### Claude Code @@ -85,6 +87,8 @@ For simple tasks, invoke an agent directly: ``` /agent worker Fix the broken pagination in the user list endpoint +/agent grunt Rename this variable consistently in one file +/agent senior Untangle this multi-file initialization bug ``` ### Codex CLI @@ -104,19 +108,19 @@ This repo uses two authored protocol files: Long-form instructions remain authored in Markdown (`agents/*.md`, `skills/*/SKILL.md`, `rules/*.md`). -Runtime policy is documented in [spec/agent-runtime-v1.md](spec/agent-runtime-v1.md) and described by [schemas/agent-runtime.schema.json](schemas/agent-runtime.schema.json). Team inventory is documented in [spec/team-protocol-v1.md](spec/team-protocol-v1.md). `generate.sh` derives tool-specific outputs for both Claude Code and [OpenAI Codex CLI](https://github.com/openai/codex). +Runtime policy is documented in [spec/agent-runtime-v1.md](spec/agent-runtime-v1.md) and described by [schemas/agent-runtime.schema.json](schemas/agent-runtime.schema.json). Team inventory is documented in [spec/team-protocol-v1.md](spec/team-protocol-v1.md). `generate.sh` derives target-specific outputs for the currently supported adapters. ### What gets generated | Source | Generated | Location | |---|---|---| -| `TEAM.yaml` + `agents/*.md` | `claude/agents/*.md` | Claude config dir | -| `TEAM.yaml` + `agents/*.md` | `codex/agents/*.toml` | Codex config dir | +| `TEAM.yaml` + `agents/*.md` | `claude/agents/*.md` | Claude adapter output | +| `TEAM.yaml` + `agents/*.md` | `codex/agents/*.toml` | Codex adapter output | | `SETTINGS.yaml` | `settings.json` (compatibility artifact, generated) | repo root | -| `SETTINGS.yaml` | `claude/settings.json` | Claude config dir | -| `SETTINGS.yaml` | `codex/config.toml` | Codex config dir | -| `TEAM.yaml` + `rules/*.md` | `codex/AGENTS.md` | Codex config dir | -| `TEAM.yaml` + `skills/*/SKILL.md` | installed skill dirs | installed skill dirs | +| `SETTINGS.yaml` | `claude/settings.json` | Claude adapter output | +| `SETTINGS.yaml` | `codex/config.toml` | Codex adapter output | +| `TEAM.yaml` + `rules/*.md` | `codex/AGENTS.md` | Codex adapter output | +| `TEAM.yaml` + `skills/*/SKILL.md` | installed skill dirs | target install output | All final config files are generated artifacts. The authored protocol sources are `SETTINGS.yaml`, `TEAM.yaml`, and Markdown instruction content. The primary workflows are `nix run .#build` / `nix run .#install` or the equivalent `just` commands. @@ -128,7 +132,7 @@ Narrow compatibility caveats: Shared runtime intent is generated conservatively across tools: -| Shared source | Claude Code | Codex CLI | +| Shared source | Claude adapter | Codex adapter | |---|---|---| | `runtime.filesystem = read-only` | `permissions.defaultMode = "plan"` | `sandbox_mode = "read-only"` | | `runtime.filesystem = workspace-write` | `permissions.defaultMode = "acceptEdits"` | `sandbox_mode = "workspace-write"` | @@ -136,7 +140,7 @@ Shared runtime intent is generated conservatively across tools: | `runtime.approval = guarded-auto` | partially represented | `approval_policy = "untrusted"` | | `runtime.approval = full-auto` | partially represented | `approval_policy = "never"` | -Codex does not support Claude's per-tool `allow` / `deny` / `ask` patterns directly. The shared protocol keeps the intent portable, then adapters derive the closest target behavior. Use target-specific fields only where there is no shared equivalent: +The adapters do not expose identical config surfaces. For example, Codex does not support Claude-style per-tool `allow` / `deny` / `ask` patterns directly. The shared protocol keeps the intent portable, then adapters derive the closest target behavior. Use target-specific fields only where there is no shared equivalent: ```yaml targets: @@ -204,9 +208,9 @@ safety: - sudo * ``` -## Model mapping +## Model mapping by target -| Claude Code | Codex CLI | +| Claude adapter | Codex adapter | |---|---| | `opus` | `gpt-5.4` | | `sonnet` | `gpt-5.3-codex` | @@ -216,7 +220,7 @@ safety: Agent body text uses `${VAR}` placeholders that are expanded per-target by `generate.sh`: -| Variable | Claude | Codex | +| Variable | Claude adapter | Codex adapter | |---|---|---| | `${PLANS_DIR}` | `.claude/plans` | `plans` | | `${WEB_SEARCH}` | `via WebFetch/WebSearch` | `via web search` | diff --git a/TEAM.yaml b/TEAM.yaml index e8b8587..5ebe433 100644 --- a/TEAM.yaml +++ b/TEAM.yaml @@ -6,8 +6,10 @@ agents: - auditor - debugger - documenter + - grunt - researcher - reviewer + - senior - worker items: architect: @@ -100,6 +102,29 @@ agents: - qa-checklist memory: project instruction_file: agents/documenter.md + grunt: + id: grunt + name: grunt + description: Fast, cheap implementer for trivial and tightly scoped work. Use for one-liners, small renames, simple edits, and low-risk mechanical tasks. Escalate when the work grows beyond that scope. + model: haiku + effort: "" + permission_mode: acceptEdits + tools: + - Read + - Write + - Edit + - Glob + - Grep + - Bash + disallowed_tools: [] + max_turns: 15 + skills: + - conventions + - worker-protocol + - message-schema + - qa-checklist + isolation: worktree + instruction_file: agents/grunt.md researcher: id: researcher name: researcher @@ -144,10 +169,33 @@ agents: - message-schema - qa-checklist instruction_file: agents/reviewer.md + senior: + id: senior + name: senior + description: Strong implementer for ambiguous, architectural, or high-risk work. Use when the task spans multiple files, requires careful judgment, or has already failed in a cheaper worker. Default escalation path for hard implementation work. + model: opus + effort: "" + permission_mode: acceptEdits + tools: + - Read + - Write + - Edit + - Glob + - Grep + - Bash + disallowed_tools: [] + max_turns: 35 + skills: + - conventions + - worker-protocol + - message-schema + - qa-checklist + isolation: worktree + instruction_file: agents/senior.md worker: id: worker name: worker - description: Universal implementer. Handles all task tiers — trivial to architectural. Model is scaled by the orchestrator based on task complexity (haiku for trivial, sonnet for standard, opus for architectural/ambiguous). Default implementer for all implementation work. + description: Balanced implementer for standard development work. Use when the task is well-defined but not trivial. Escalate upward for architectural ambiguity and downward for tiny mechanical changes. model: sonnet effort: "" permission_mode: acceptEdits diff --git a/agents/debugger.md b/agents/debugger.md index fff6eec..b39a645 100644 --- a/agents/debugger.md +++ b/agents/debugger.md @@ -44,7 +44,7 @@ Run the test or repro case again. Confirm the bug is gone. Check that adjacent t - Cannot reproduce: report exactly what you tried and what happened - Root cause unclear after 2 hypotheses: report your findings and the two best hypotheses — do not guess -- Fix requires architectural change: report the root cause and flag for senior-worker escalation +- Fix requires architectural change: report the root cause and flag for `senior` escalation ## Scope constraint diff --git a/agents/grunt.md b/agents/grunt.md new file mode 100644 index 0000000..b243899 --- /dev/null +++ b/agents/grunt.md @@ -0,0 +1,37 @@ +--- +name: grunt +description: Fast, cheap implementer for trivial and tightly scoped work. Use for one-liners, small renames, simple edits, and low-risk mechanical tasks. Escalate when the work grows beyond that scope. +model: haiku +permissionMode: acceptEdits +isolation: worktree +tools: Read, Write, Edit, Glob, Grep, Bash +maxTurns: 15 +skills: + - conventions + - worker-protocol + - message-schema + - qa-checklist +--- + +You are a grunt agent. You implement small, explicit tasks quickly and cheaply. + +## Behavioral constraints + +Implement only what was assigned. Do not expand scope on your own judgment. + +**Do not make architectural decisions.** If the task depends on an unclear interface, missing contract, or non-trivial judgment call, stop and report that the task should be escalated. + +If the task grows beyond a small, tightly scoped change, stop and report that it should be reassigned to `worker`. Escalate to the orchestrator instead when the real issue is a missing plan, unclear requirement, or changed scope. + +If you are stuck after one focused attempt, stop and report what blocked you. + +## Escalation contract + +- Stay local: one-file or tightly bounded edits, obvious fixes, and low-risk mechanical work. +- Escalate to `worker`: when the task now needs broader implementation work, multiple meaningful files, or more than mechanical judgment. +- Escalate to the orchestrator: when the assignment is underspecified, the plan appears wrong, or the scope changed materially from what you were given. +- Do not escalate directly to `senior` unless the orchestrator explicitly told you to route there. + +When returning a typed envelope: +- Use `signal: blocked` when stronger implementation or orchestrator intervention is needed. +- In the body, state the preferred next route explicitly: `Route: worker` or `Route: orchestrator`. diff --git a/agents/senior.md b/agents/senior.md new file mode 100644 index 0000000..1d7e4ea --- /dev/null +++ b/agents/senior.md @@ -0,0 +1,38 @@ +--- +name: senior +description: Strong implementer for ambiguous, architectural, or high-risk work. Use when the task spans multiple files, requires careful judgment, or has already failed in a cheaper worker. Default escalation path for hard implementation work. +model: opus +permissionMode: acceptEdits +isolation: worktree +tools: Read, Write, Edit, Glob, Grep, Bash +maxTurns: 35 +skills: + - conventions + - worker-protocol + - message-schema + - qa-checklist +--- + +You are a senior agent. You implement difficult or ambiguous tasks with strong technical judgment. + +## Behavioral constraints + +Implement only what was assigned. Do not expand scope unless the orchestrator explicitly revises the task. + +You may resolve local implementation ambiguity when necessary, but **do not invent architecture** that should have been specified by the plan. If a missing interface or contract changes the design boundary, stop and report the gap. + +If the plan appears wrong or incomplete, stop and explain the issue clearly rather than forcing a brittle implementation. + +If you are stuck after two serious attempts, stop and report what you tried and what remains unresolved. + +## Escalation contract + +- Stay local: difficult implementation, careful cross-file reasoning, and bounded ambiguity that can be resolved without changing the plan's design boundary. +- Escalate to the orchestrator: when the remaining work should be decomposed into a team, when coordination is now the main risk, or when the plan needs to be revised before safe implementation can continue. +- Do not summon more seniors yourself. Re-decomposition is the orchestrator's responsibility. +- If a stronger implementation wave is needed, report that explicitly so the orchestrator can spawn a senior team with clear ownership. + +When returning a typed envelope: +- Use `signal: blocked` when the orchestrator should re-decompose the work, amend the plan, or split the task into a senior wave. +- Use `signal: escalate` only when the issue requires a user decision rather than orchestration. +- In the body, state the preferred next route explicitly: `Route: orchestrator (re-decompose)` or `Route: orchestrator (user decision required)`. diff --git a/agents/worker.md b/agents/worker.md index 696b851..d0e4b93 100644 --- a/agents/worker.md +++ b/agents/worker.md @@ -1,6 +1,6 @@ --- name: worker -description: Universal implementer. Handles all task tiers — trivial to architectural. Model is scaled by the orchestrator based on task complexity (haiku for trivial, sonnet for standard, opus for architectural/ambiguous). Default implementer for all implementation work. +description: Balanced implementer for standard development work. Use when the task is well-defined but not trivial. Escalate upward for architectural ambiguity and downward for tiny mechanical changes. model: sonnet permissionMode: acceptEdits isolation: worktree @@ -13,7 +13,7 @@ skills: - qa-checklist --- -You are a worker agent. You implement what you are assigned. Your orchestrator may resume you to iterate on feedback or continue related work. +You are a worker agent. You implement standard development tasks. Your orchestrator may resume you to iterate on feedback or continue related work. ## Behavioral constraints @@ -23,4 +23,16 @@ Implement only what was assigned. Do not expand scope on your own judgment — i If you are stuck after two attempts at the same approach, stop and report what you tried and why it failed. -If this task is more complex than it appeared (more files involved, unclear interfaces, systemic implications), flag that to the orchestrator — it may need to be re-dispatched with a more capable model or a revised plan. +If this task is more complex than it appeared (more files involved, unclear interfaces, systemic implications), stop and report whether the issue is implementation difficulty or a planning gap. + +## Escalation contract + +- Stay local: standard, well-defined implementation work where the plan and interfaces are already clear. +- Escalate to `senior`: when the task is implementable but now requires stronger judgment, broader reasoning, or higher-risk multi-file work than originally assigned. +- Escalate to the orchestrator: when the plan is incomplete, an interface or requirement is missing, or proceeding would require making an architectural decision that was not assigned. +- Do not silently turn a plan gap into a design decision. + +When returning a typed envelope: +- Use `signal: blocked` when the work should be reassigned to `senior` or when the orchestrator needs to unblock you. +- Use `signal: escalate` only when user-level clarification or approval is required. +- In the body, state the preferred next route explicitly: `Route: senior` or `Route: orchestrator`. diff --git a/schemas/team.schema.json b/schemas/team.schema.json index 1710b80..3be7545 100644 --- a/schemas/team.schema.json +++ b/schemas/team.schema.json @@ -248,8 +248,10 @@ "auditor", "debugger", "documenter", + "grunt", "researcher", "reviewer", + "senior", "worker" ] }, @@ -261,8 +263,10 @@ "auditor", "debugger", "documenter", + "grunt", "researcher", "reviewer", + "senior", "worker" ], "properties": { @@ -306,6 +310,16 @@ } ] }, + "grunt": { + "allOf": [ + { "$ref": "#/$defs/agent_item" }, + { + "properties": { + "id": { "const": "grunt" } + } + } + ] + }, "researcher": { "allOf": [ { "$ref": "#/$defs/agent_item" }, @@ -326,6 +340,16 @@ } ] }, + "senior": { + "allOf": [ + { "$ref": "#/$defs/agent_item" }, + { + "properties": { + "id": { "const": "senior" } + } + } + ] + }, "worker": { "allOf": [ { "$ref": "#/$defs/agent_item" }, diff --git a/skills/message-schema/SKILL.md b/skills/message-schema/SKILL.md index 240db64..8f11d2d 100644 --- a/skills/message-schema/SKILL.md +++ b/skills/message-schema/SKILL.md @@ -73,6 +73,19 @@ Optional: `ac_coverage` (omit when no AC provided in assignment) Body: `## Result` section with implementation details, then `## Self-Assessment` with per-criterion notes and known limitations. +**Routing contract for implementers:** +- `grunt` uses `blocked` to request reassignment to `worker` or orchestrator intervention. +- `worker` uses `blocked` to request reassignment to `senior` or orchestrator intervention. +- `senior` uses `blocked` to request orchestrator re-decomposition, plan revision, or a senior wave/team. +- Any implementer uses `escalate` only when the blocker requires a user decision or approval, not merely a stronger implementer. + +When `signal: blocked` or `signal: escalate` is used, the body must include a one-line route hint: +- `Route: worker` +- `Route: senior` +- `Route: orchestrator` +- `Route: orchestrator (re-decompose)` +- `Route: orchestrator (user decision required)` + ### review_verdict Emitted by: reviewer diff --git a/skills/orchestrate/SKILL.md b/skills/orchestrate/SKILL.md index 9176940..3e2b8da 100644 --- a/skills/orchestrate/SKILL.md +++ b/skills/orchestrate/SKILL.md @@ -10,7 +10,9 @@ You are now acting as orchestrator. Decompose, delegate, validate, deliver. Neve ``` You (orchestrator) - ├── worker (sonnet default — haiku for trivial, opus for architectural) + ├── grunt (haiku) — trivial, cheap implementer + ├── worker (sonnet) — standard implementer + ├── senior (opus) — ambiguous, architectural, or high-risk implementer ├── debugger (sonnet) — bug diagnosis and minimal fixes ├── documenter (sonnet) — documentation only, never touches source ├── researcher (sonnet) — one per topic, parallel fact-finding @@ -27,14 +29,14 @@ Determine before starting. Default to the lowest applicable tier. | Tier | Scope | Approach | |---|---|---| -| **0** | Trivial (typo, rename, one-liner) | Spawn worker (haiku). No review. Ship directly. | -| **1** | Single straightforward task | Spawn worker → reviewer → ship or iterate | +| **0** | Trivial (typo, rename, one-liner) | Spawn `grunt`. No review. Ship directly. | +| **1** | Single straightforward task | Spawn `worker` → reviewer → ship or iterate | | **2** | Multi-task or complex | Full pipeline: architect → parallel workers (waves) → parallel review | | **3** | Multi-session, project-scale | Full pipeline. Set milestones with the user. Background architect. | **Cost-aware shortcuts:** -- Tier 0: skip planning entirely, spawn worker with `model: haiku` -- Tier 1 with obvious approach: spawn worker directly, skip architect +- Tier 0: skip planning entirely, spawn `grunt` +- Tier 1 with obvious approach: spawn `worker` directly, skip architect - Tier 1 with uncertain approach: spawn architect (Phase 1 triage only, skip research) - Tier 2+: run the full pipeline @@ -46,7 +48,7 @@ Determine before starting. Default to the lowest applicable tier. What is actually being asked vs. implied? If ambiguous, ask one focused question. Don't ask for what you can discover yourself. ### Step 2 — Determine tier -Tier 0: spawn worker directly with `model: haiku`. No decomposition, no review. Deliver and stop. +Tier 0: spawn `grunt` directly. No decomposition, no review. Deliver and stop. ### Step 3 — Plan (Tier 1 with uncertain approach, or Tier 2+) @@ -88,10 +90,10 @@ For each wave in the plan: 2. Each worker receives: their task spec, the plan file path, interface contracts, out-of-scope constraint, and relevant file list. -3. Select model based on task complexity: - - Trivial, well-scoped: `model: haiku` - - Standard implementation: `model: sonnet` (default) - - Architectural reasoning, ambiguous requirements, systemic changes: `model: opus` +3. Select the implementer based on task complexity: + - Trivial, well-scoped: `grunt` + - Standard implementation: `worker` + - Architectural reasoning, ambiguous requirements, systemic changes: `senior` 4. Wait for all workers in the wave to complete before advancing. @@ -99,6 +101,12 @@ For each wave in the plan: **Workers must not make architectural decisions.** If a worker flags a gap in the plan, resolve it before re-dispatching — either update the plan or provide explicit guidance. +**Escalation routing:** +- `grunt -> worker` when the task is no longer mechanical but still well-defined +- `worker -> senior` when the task is implementable but needs stronger judgment or broader reasoning +- `grunt` or `worker` -> orchestrator when the real issue is a plan gap, changed scope, or missing requirement +- `senior -> orchestrator` when the work should be re-decomposed into a senior wave/team or the plan boundary must change + ### Step 6 — Review After each wave, spawn `reviewer` and `auditor` in a single response. They run in parallel. @@ -126,9 +134,10 @@ Do not advance until both verdicts are collected. - Iterations 4–5: fix CRITICAL only. Ship MODERATE/MINOR as PASS WITH NOTES. **Termination rules:** -- Same issue 3 consecutive iterations → re-dispatch as worker with `model: opus` and full history +- Same issue 3 consecutive iterations → re-dispatch to `senior` with full history - 5 review cycles max → deliver what exists, disclose unresolved issues - Reviewer vs. requirement conflict → stop, escalate to user with both sides +- If a `senior` reports `Route: orchestrator (re-decompose)`, stop iterating locally and re-plan before further dispatch ### Step 8 — Aggregate and deliver (Tier 2+) @@ -147,9 +156,9 @@ Lead with the result. Don't expose worker IDs, wave counts, or internal mechanic | Condition | Agent | Model override | |---|---|---| -| Trivial one-liner, rename, typo | `worker` | `haiku` | -| Well-defined task, clear approach | `worker` | `sonnet` (default) | -| Architectural reasoning, ambiguous requirements, systemic changes, worker failures | `worker` | `opus` | +| Trivial one-liner, rename, typo | `grunt` | — | +| Well-defined task, clear approach | `worker` | — | +| Architectural reasoning, ambiguous requirements, systemic changes, worker failures | `senior` | — | | Bug diagnosis and fixing | `debugger` | — | | Documentation only, never modify source | `documenter` | — | @@ -170,7 +179,7 @@ When multiple risk tags are present, take the union. Spawn all required reviewer ### Agent lifecycles -**worker / debugger / documenter** +**grunt / worker / senior / debugger / documenter** - Resume when iterating on the same task or closely related follow-up - Spawn fresh when: fundamentally wrong path, re-dispatching with different model, requirements changed, agent is thrashing @@ -229,7 +238,14 @@ All agent communication uses typed YAML frontmatter envelopes defined in the `me | `signal: plan_complete` | architect → you | Read plan file, begin wave dispatch | | `signal: research_complete` | researcher → you | Collect, assemble into Research Context | | `signal: blocked` (`plan_result`) | architect → you | Escalate to user before dispatching workers | -| `signal: blocked` (`worker_submission`) | worker → you | Investigate blocker, unblock or reassign | +| `signal: blocked` (`worker_submission`) | implementer → you | Route by the envelope's explicit next-step hint | | `signal: escalate` | any → you | Escalate to user with context | +Implementer route handling: +- `Route: worker` -> reassign to `worker` +- `Route: senior` -> reassign to `senior` +- `Route: orchestrator` -> amend the plan or provide explicit guidance before redispatch +- `Route: orchestrator (re-decompose)` -> re-run architect or split into a senior wave/team with explicit ownership +- `Route: orchestrator (user decision required)` -> take the issue to the user + When dispatching agents, use the orchestrator→agent envelope types (`task_assignment`, `revision_request`, `approval`, `triage_request`, `architecture_request`, `research_request`) from the message-schema skill. From 28ab10c58f223b30e6671d958da247ae047cf51c Mon Sep 17 00:00:00 2001 From: Bryan Ramos <bryan@ramos.codes> Date: Thu, 2 Apr 2026 15:40:57 -0400 Subject: [PATCH 35/36] done --- agents/architect.md | 7 +++++-- agents/auditor.md | 13 +++++++------ agents/documenter.md | 11 ++++++----- agents/researcher.md | 4 ++-- agents/reviewer.md | 4 ++-- 5 files changed, 22 insertions(+), 17 deletions(-) diff --git a/agents/architect.md b/agents/architect.md index c180268..5eb6de5 100644 --- a/agents/architect.md +++ b/agents/architect.md @@ -4,7 +4,8 @@ description: Research-first planning agent. Handles triage, research coordinatio model: opus effort: max permissionMode: plan -tools: Read, Glob, Grep, WebFetch, WebSearch, Bash, Write +tools: Read, Glob, Grep, WebFetch, WebSearch, Write +disallowedTools: Edit maxTurns: 35 skills: - conventions @@ -17,6 +18,8 @@ Never implement anything. Never modify source files. Analyze, evaluate, plan. **Plan persistence:** Always write the approved plan to `${PLANS_DIR}/<kebab-case-title>.md`. Never return the plan inline without writing it first. Check whether a plan file already exists before writing — if it does, continue from it. +**Write boundary:** You have write capability only so you can persist plan files. This is not path-enforced by tooling. You must treat writes outside `${PLANS_DIR}/` as forbidden. + Frontmatter format: ``` --- @@ -27,7 +30,7 @@ status: active --- ``` -**Bash is read-only:** `git log`, `git diff`, `git show`, `ls`, `cat`, `find`. Never mkdir, touch, rm, cp, mv, git add, git commit, or any state-changing command. +**No Bash execution:** perform repository inspection with Read/Glob/Grep/WebFetch/WebSearch only. --- diff --git a/agents/auditor.md b/agents/auditor.md index 3a26d67..69d1c88 100644 --- a/agents/auditor.md +++ b/agents/auditor.md @@ -3,6 +3,7 @@ name: auditor description: Use after implementation — audits for security vulnerabilities and validates runtime behavior. Builds, tests, and probes acceptance criteria. Never modifies code. model: sonnet background: true +permissionMode: acceptEdits tools: Read, Glob, Grep, Bash, WebFetch, WebSearch disallowedTools: Write, Edit maxTurns: 25 @@ -14,7 +15,7 @@ skills: You are an auditor. You do two things: security analysis and runtime validation. Never write, edit, or fix code — only identify, validate, and report. -**Bash is for validation only** — run builds, tests, type checks, and read-only inspection commands. Never use it to modify files. +Shell access is available for build, test, typecheck, and probe commands. You still must not modify code, install dependencies globally, or make workspace edits. --- @@ -53,10 +54,10 @@ For every security finding: explain the attack vector, reference the relevant CW ## Runtime validation -- **Build** — run the build command and report errors -- **Tests** — run tests most relevant to the changed code; not the full suite unless asked -- **Type-check** — run the type checker if the project has one -- **Adversarial probes** — exercise edge cases, error paths, and boundary conditions against the stated acceptance criteria +- **Build** — run the relevant build command when the project exposes one; otherwise validate from available CI logs, prior run artifacts, or explicit evidence provided by implementers +- **Tests** — run targeted test commands when feasible; otherwise validate from available test reports, prior run artifacts, or explicit evidence provided by implementers +- **Type-check** — run the relevant typecheck/lint/static-analysis command when feasible; otherwise validate from available reports or explicit evidence +- **Adversarial probes** — evaluate edge cases, error paths, and boundary conditions with executable checks when possible; if no executable path exists, mark as skipped with notes --- @@ -103,4 +104,4 @@ Then the markdown body: --- -If the project has no tests, cannot be built, or the test runner is missing, use `test_status: skipped` and `signal: pass_with_notes` with an explanation of what could and could not be verified. Do not flag theoretical issues that require conditions outside the threat model. +If executable verification is unavailable, infeasible, or unsupported by the project, use `build_status: skipped`, `test_status: skipped`, and `typecheck_status: skipped` as appropriate with `signal: pass_with_notes`, and explain exactly what could and could not be verified. Do not flag theoretical issues that require conditions outside the threat model. diff --git a/agents/documenter.md b/agents/documenter.md index adaa706..9855584 100644 --- a/agents/documenter.md +++ b/agents/documenter.md @@ -1,10 +1,11 @@ --- name: documenter -description: Use when asked to write or update documentation — READMEs, API references, architecture overviews, inline doc comments, or changelogs. Reads code first, writes accurate docs. Never modifies source code. +description: Use when asked to write or update documentation — READMEs, API references, architecture overviews, inline doc comments, or changelogs. Reads code first and updates documentation artifacts only. model: sonnet effort: high memory: project -tools: Read, Write, Edit, Glob, Grep, Bash +permissionMode: acceptEdits +tools: Read, Write, Edit, Glob, Grep maxTurns: 20 skills: - conventions @@ -13,14 +14,14 @@ skills: - qa-checklist --- -You are a documentation specialist. Your job is to read code and produce accurate, well-structured documentation. You never modify source code — only documentation files and doc comments. +You are a documentation specialist. Your job is to read code and produce accurate, well-structured documentation. You only modify documentation artifacts, and must not change runtime behavior. ## What you document - **READMEs** — project overview, setup, usage, examples - **API references** — function/method signatures, parameters, return values, errors - **Architecture docs** — how components fit together, data flows, design decisions -- **Inline doc comments** — docstrings, JSDoc, rustdoc, godoc — where explicitly asked +- **Inline doc comments** — docstrings, JSDoc, rustdoc, godoc — where explicitly requested - **Changelogs / migration guides** — what changed and how to upgrade ## How you operate @@ -39,6 +40,6 @@ You are a documentation specialist. Your job is to read code and produce accurat ## What you do NOT do -- Modify source code, even to add inline comments unless explicitly asked +- Modify executable logic or non-documentation behavior - Invent behavior or fill gaps with plausible-sounding descriptions - Generate boilerplate docs that don't reflect actual code diff --git a/agents/researcher.md b/agents/researcher.md index df773d0..c8d91e4 100644 --- a/agents/researcher.md +++ b/agents/researcher.md @@ -3,7 +3,7 @@ name: researcher description: Use to answer a specific research question with verified facts. Spawned in parallel — one instance per topic. Stateless. Returns verified facts, source URLs, and gotchas. model: sonnet permissionMode: plan -tools: Read, Glob, Grep, Bash, WebFetch, WebSearch +tools: Read, Glob, Grep, WebFetch, WebSearch disallowedTools: Write, Edit maxTurns: 10 skills: @@ -12,7 +12,7 @@ skills: You are a researcher. You answer one specific research question with verified facts. You never implement, plan, or make architectural decisions — you find and verify information. -**Bash is for read-only inspection only.** Never use Bash for commands that change state. +Shell access is intentionally unavailable in this role to enforce read-only behavior. ## How you operate diff --git a/agents/reviewer.md b/agents/reviewer.md index 41e2478..aaa2d58 100644 --- a/agents/reviewer.md +++ b/agents/reviewer.md @@ -3,7 +3,7 @@ name: reviewer description: Use after implementation — reviews code quality and verifies claims against source, docs, and acceptance criteria. Never modifies code. model: sonnet permissionMode: plan -tools: Read, Glob, Grep, Bash, WebFetch, WebSearch +tools: Read, Glob, Grep, WebFetch, WebSearch disallowedTools: Write, Edit maxTurns: 20 skills: @@ -14,7 +14,7 @@ skills: You are a reviewer. You do two things in one pass: quality review and claim verification. Never write, edit, or fix code — only flag and explain. -**Bash is for verification only** — run type checks, lint, build checks, or spot-check commands. Never modify files. +Shell access is intentionally unavailable in this role to enforce read-only behavior. ## Quality review From 7381316e28cb92f0ee5e63bf62ff3b1339531ec0 Mon Sep 17 00:00:00 2001 From: Bryan Ramos <bryan@ramos.codes> Date: Thu, 2 Apr 2026 15:46:38 -0400 Subject: [PATCH 36/36] fix --- CLAUDE.md | 2 +- README.md | 16 ++-- SETTINGS.yaml | 7 +- TEAM.yaml | 13 ++- flake.nix | 33 +++++++ generate.sh | 55 +++++++++--- install.sh | 143 ++++++++++++++++++++++-------- rules/01-session.md | 1 + schemas/agent-runtime.schema.json | 6 +- schemas/team.schema.json | 2 +- spec/agent-runtime-v1.md | 22 +++-- spec/team-protocol-v1.md | 5 +- 12 files changed, 230 insertions(+), 75 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index cf054e5..4391add 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,4 +1,4 @@ # Global Claude Code Instructions Rules are modularized in `rules/` and loaded automatically by the generated Claude config. -Agent-team specific protocols live in skills (orchestrate, conventions, worker-protocol, qa-checklist, message-schema, project). +Agent-team specific protocols live in skills (orchestrate, conventions, worker-protocol, qa-checklist, message-schema). diff --git a/README.md b/README.md index b780659..07ad725 100644 --- a/README.md +++ b/README.md @@ -65,7 +65,6 @@ just clean # removes generated artifacts: settings.json + claude/ + cod | `worker-protocol` | Output format, feedback handling, and operational procedures for worker agents | | `qa-checklist` | Self-validation checklist workers run before returning results | | `message-schema` | Typed YAML frontmatter envelopes for all inter-agent communication | -| `project` | Instructs agents to check for and ingest a project-specific skill file before starting work | ## Rules @@ -120,6 +119,7 @@ Runtime policy is documented in [spec/agent-runtime-v1.md](spec/agent-runtime-v1 | `SETTINGS.yaml` | `claude/settings.json` | Claude adapter output | | `SETTINGS.yaml` | `codex/config.toml` | Codex adapter output | | `TEAM.yaml` + `rules/*.md` | `codex/AGENTS.md` | Codex adapter output | +| `TEAM.yaml` + `skills/*/SKILL.md` | `codex/skills -> ../skills` | Codex adapter output | | `TEAM.yaml` + `skills/*/SKILL.md` | installed skill dirs | target install output | All final config files are generated artifacts. The authored protocol sources are `SETTINGS.yaml`, `TEAM.yaml`, and Markdown instruction content. The primary workflows are `nix run .#build` / `nix run .#install` or the equivalent `just` commands. @@ -129,6 +129,7 @@ Narrow compatibility caveats: - TEAM schema is intentionally rigid/repo-specific in v1. Inventory changes require schema updates in lockstep. - Claude generated agent frontmatter is normalized by generator serialization (field order/quoting), which may produce non-semantic diffs. - Codex skill installation is TEAM-authoritative when `TEAM.yaml` is present. Legacy directory fallback is used only when TEAM is absent or unparseable. +- Codex custom-agent files do not preserve every TEAM agent field. `background`, `memory`, and `isolation` have no documented per-agent equivalents in current Codex docs. TEAM `skills` are mapped into per-agent Codex `skills.config` entries. Shared runtime intent is generated conservatively across tools: @@ -140,7 +141,13 @@ Shared runtime intent is generated conservatively across tools: | `runtime.approval = guarded-auto` | partially represented | `approval_policy = "untrusted"` | | `runtime.approval = full-auto` | partially represented | `approval_policy = "never"` | -The adapters do not expose identical config surfaces. For example, Codex does not support Claude-style per-tool `allow` / `deny` / `ask` patterns directly. The shared protocol keeps the intent portable, then adapters derive the closest target behavior. Use target-specific fields only where there is no shared equivalent: +The adapters do not expose identical config surfaces. For example, Codex does not support Claude-style per-tool `allow` / `deny` / `ask` patterns directly. The shared protocol keeps the intent portable, then adapters derive the closest target behavior. + +`runtime.approval` and `runtime.network_access` are the primary source of truth. `targets.codex.approval_policy` and `targets.codex.network_access` are compatibility overrides for exceptional cases only. When set, they override the Codex-derived value. + +This repo intentionally sets those Codex overrides to `approval_policy: never` and `network_access: true`. The reason is not that Codex has no approval controls at all, but that it lacks Claude-equivalent pattern-level permission controls for tool/path `allow` / `deny` / `ask`. In this repo, Codex therefore runs with a deliberately more permissive top-level policy than the portable runtime defaults. + +Use target-specific fields only when you intentionally need a target-only override: ```yaml targets: @@ -234,7 +241,6 @@ Each project repo can extend the team with local config in `.claude/`: - `.claude/CLAUDE.md` — project-specific instructions (architecture notes, domain conventions, stack details) - `.claude/agents/` — project-local agent overrides or additions -- `.claude/skills/project.md` — skill file that agents automatically ingest before starting work (see the `project` skill) Commit `.claude/` with the project so the team has context wherever it runs. @@ -242,7 +248,7 @@ Commit `.claude/` with the project so the team has context wherever it runs. Two memory systems coexist: -- **Manual memory** (`.claude/memory/`) — curated context files with YAML frontmatter, indexed by `MEMORY.md`. Loaded as part of the CLAUDE.md hierarchy on every session. Use this for project decisions, user preferences, and reference pointers. +- **Project memory** (`memory/`) — curated context files with YAML frontmatter, indexed by `MEMORY.md`. This is the portable, instruction-level memory source shared across targets. - **Agent memory** (`.claude/agent-memory/`) — Claude Code's built-in runtime memory, written automatically by agents with `memory: project` scope. Excluded from CLAUDE.md context via `claudeMdExcludes` to avoid polluting the context window. -Commit both directories with the repo so memory persists across machines and sessions. +Commit both directories when used so memory persists across machines and sessions. diff --git a/SETTINGS.yaml b/SETTINGS.yaml index e5cd4f5..0844c7b 100644 --- a/SETTINGS.yaml +++ b/SETTINGS.yaml @@ -47,5 +47,8 @@ targets: claude_md_excludes: - .claude/agent-memory/** codex: - approval_policy: untrusted - network_access: false + # Intentional target override: Codex does not expose Claude-equivalent + # per-tool/path allow/deny/ask controls, so this repo runs Codex in + # full-auto with network enabled by default. + approval_policy: never + network_access: true diff --git a/TEAM.yaml b/TEAM.yaml index 5ebe433..379150b 100644 --- a/TEAM.yaml +++ b/TEAM.yaml @@ -25,9 +25,9 @@ agents: - Grep - WebFetch - WebSearch - - Bash - Write - disallowed_tools: [] + disallowed_tools: + - Edit max_turns: 35 skills: - conventions @@ -39,7 +39,7 @@ agents: description: Use after implementation — audits for security vulnerabilities and validates runtime behavior. Builds, tests, and probes acceptance criteria. Never modifies code. model: sonnet effort: "" - permission_mode: "" + permission_mode: acceptEdits tools: - Read - Glob @@ -82,17 +82,16 @@ agents: documenter: id: documenter name: documenter - description: Use when asked to write or update documentation — READMEs, API references, architecture overviews, inline doc comments, or changelogs. Reads code first, writes accurate docs. Never modifies source code. + description: Use when asked to write or update documentation — READMEs, API references, architecture overviews, inline doc comments, or changelogs. Reads code first and updates documentation artifacts only. model: sonnet effort: high - permission_mode: "" + permission_mode: acceptEdits tools: - Read - Write - Edit - Glob - Grep - - Bash disallowed_tools: [] max_turns: 20 skills: @@ -136,7 +135,6 @@ agents: - Read - Glob - Grep - - Bash - WebFetch - WebSearch disallowed_tools: @@ -157,7 +155,6 @@ agents: - Read - Glob - Grep - - Bash - WebFetch - WebSearch disallowed_tools: diff --git a/flake.nix b/flake.nix index 3b96dd7..dbbf296 100644 --- a/flake.nix +++ b/flake.nix @@ -60,6 +60,22 @@ validate(instance=settings_data, schema=settings_schema) validate(instance=team_data, schema=team_schema) + + # TEAM referenced files must exist on disk. + for agent_id in team_data["agents"]["order"]: + instruction_file = team_data["agents"]["items"][agent_id]["instruction_file"] + if not (root / instruction_file).is_file(): + raise FileNotFoundError(f"Missing agent instruction file: {instruction_file}") + + for skill_id in team_data["skills"]["order"]: + instruction_file = team_data["skills"]["items"][skill_id]["instruction_file"] + if not (root / instruction_file).is_file(): + raise FileNotFoundError(f"Missing skill instruction file: {instruction_file}") + + for rule_id in team_data["rules"]["order"]: + source_file = team_data["rules"]["items"][rule_id]["source_file"] + if not (root / source_file).is_file(): + raise FileNotFoundError(f"Missing rule source file: {source_file}") PY ''; @@ -104,6 +120,7 @@ program = "${mkAppScript "install" '' set -euo pipefail test -f ./install.sh || { echo "Run this command from the repository root."; exit 1; } + ${validateCmd} ${bashBin} ./install.sh ''}/bin/install"; meta.description = "Install generated artifacts into Claude and Codex config directories."; @@ -148,6 +165,22 @@ validate(instance=settings_data, schema=settings_schema) validate(instance=team_data, schema=team_schema) + + # TEAM referenced files must exist on disk. + for agent_id in team_data["agents"]["order"]: + instruction_file = team_data["agents"]["items"][agent_id]["instruction_file"] + if not (root / instruction_file).is_file(): + raise FileNotFoundError(f"Missing agent instruction file: {instruction_file}") + + for skill_id in team_data["skills"]["order"]: + instruction_file = team_data["skills"]["items"][skill_id]["instruction_file"] + if not (root / instruction_file).is_file(): + raise FileNotFoundError(f"Missing skill instruction file: {instruction_file}") + + for rule_id in team_data["rules"]["order"]: + source_file = team_data["rules"]["items"][rule_id]["source_file"] + if not (root / source_file).is_file(): + raise FileNotFoundError(f"Missing rule source file: {source_file}") PY ''; diff --git a/generate.sh b/generate.sh index abd2123..5b8002a 100755 --- a/generate.sh +++ b/generate.sh @@ -435,11 +435,11 @@ map_default_sandbox_mode() { # --------------------------------------------------------------------------- # map_approval_policy — determines Codex approval_policy from shared config -# $1 = Claude permissions.defaultMode value +# $1 = runtime.approval value (manual / guarded-auto / full-auto) # $2 = optional Codex approval override from shared config # --------------------------------------------------------------------------- map_approval_policy() { - local default_mode="$1" + local runtime_approval="$1" local override="$2" if [ -n "$override" ] && [ "$override" != "null" ]; then @@ -447,14 +447,7 @@ map_approval_policy() { return fi - # Closest intent mapping: - # - plan remains guarded - # - acceptEdits becomes Codex's closest "edit without constant prompts" mode - case "$default_mode" in - plan) echo "on-request" ;; - acceptEdits) echo "untrusted" ;; - *) echo "untrusted" ;; - esac + map_approval_intent_to_codex_policy "$runtime_approval" } # --------------------------------------------------------------------------- @@ -560,6 +553,8 @@ generate_codex() { # Clean and recreate output directories rm -rf "$CODEX_DIR" mkdir -p "$CODEX_AGENTS_DIR" + ln -s ../skills "$CODEX_DIR/skills" + echo "Symlinked: $CODEX_DIR/skills -> ../skills" # Generate agent .toml files from TEAM metadata + markdown instruction body echo "Generating Codex agent definitions..." @@ -568,6 +563,7 @@ generate_codex() { [ -n "$agent_id" ] || continue local name description model effort permission_mode tools disallowed_tools + local agent_skills local src_file dst_file name="$(yq -r ".agents.items.${agent_id}.name" "$TEAM_YAML")" description="$(yq -r ".agents.items.${agent_id}.description" "$TEAM_YAML")" @@ -576,6 +572,7 @@ generate_codex() { permission_mode="$(yq -r ".agents.items.${agent_id}.permission_mode // \"\"" "$TEAM_YAML")" tools="$(yq -r ".agents.items.${agent_id}.tools[]" "$TEAM_YAML" | csv_from_yaml_array)" disallowed_tools="$(yq -r ".agents.items.${agent_id}.disallowed_tools // [] | .[]" "$TEAM_YAML" | csv_from_yaml_array)" + agent_skills="$(yq -r ".agents.items.${agent_id}.skills[]" "$TEAM_YAML")" src_file="$SCRIPT_DIR/$(yq -r ".agents.items.${agent_id}.instruction_file" "$TEAM_YAML")" dst_file="$CODEX_AGENTS_DIR/${name}.toml" @@ -599,6 +596,13 @@ generate_codex() { You do NOT have access to these tools: ${disallowed_tools}" fi + # TOML multiline basic strings use """ delimiters; reject raw delimiter + # sequences in instruction bodies so generated TOML remains parseable. + if printf '%s' "$developer_instructions" | grep -q '"""'; then + echo "Error: agent instruction contains raw triple quotes (\"\"\") which break TOML in $src_file" + exit 1 + fi + # Write TOML output cat > "$dst_file" <<TOML name = "${name}" @@ -606,11 +610,35 @@ description = "${description}" model = "${codex_model}" model_reasoning_effort = "${codex_effort}" sandbox_mode = "${codex_sandbox}" +TOML + + cat >> "$dst_file" <<TOML developer_instructions = """ ${developer_instructions} """ TOML + local skill_id skill_applies enabled + while IFS= read -r skill_id; do + [ -n "$skill_id" ] || continue + skill_applies="$(yq -r ".skills.items.${skill_id}.applies_to[]" "$TEAM_YAML")" + if ! printf '%s\n' "$skill_applies" | grep -qx "codex"; then + continue + fi + + enabled=false + if printf '%s\n' "$agent_skills" | grep -qx "$skill_id"; then + enabled=true + fi + + cat >> "$dst_file" <<TOML +[[skills.config]] +path = "../skills/${skill_id}/SKILL.md" +enabled = ${enabled} + +TOML + done < <(yq -r '.skills.order[]' "$TEAM_YAML") + echo "Generated: $dst_file" done < <(yq -r '.agents.order[]' "$TEAM_YAML") @@ -620,7 +648,7 @@ TOML { echo "# Agent Team Instructions" echo "" - echo "Agent-team specific protocols live in skills (orchestrate, conventions, worker-protocol, qa-checklist, message-schema, project)." + echo "Agent-team specific protocols live in skills (orchestrate, conventions, worker-protocol, qa-checklist, message-schema)." local rule_id rules_file while IFS= read -r rule_id; do [ -n "$rule_id" ] || continue @@ -636,14 +664,15 @@ TOML echo "" echo "Generating codex/config.toml..." - local default_mode codex_approval_override codex_network_access + local default_mode runtime_approval codex_approval_override codex_network_access default_mode="$(map_filesystem_intent_to_claude_mode "$(yq -r '.runtime.filesystem' "$SETTINGS_SHARED_YAML")")" + runtime_approval="$(yq -r '.runtime.approval' "$SETTINGS_SHARED_YAML")" codex_approval_override="$(yq -r '.targets.codex.approval_policy // ""' "$SETTINGS_SHARED_YAML")" codex_network_access="$(yq -r '.targets.codex.network_access // .runtime.network_access // false' "$SETTINGS_SHARED_YAML")" local config_sandbox config_approval config_sandbox="$(map_default_sandbox_mode "$default_mode")" - config_approval="$(map_approval_policy "$default_mode" "$codex_approval_override")" + config_approval="$(map_approval_policy "$runtime_approval" "$codex_approval_override")" cat > "$CODEX_DIR/config.toml" <<TOML #:schema https://developers.openai.com/codex/config-schema.json diff --git a/install.sh b/install.sh index 2eb0475..33b2902 100755 --- a/install.sh +++ b/install.sh @@ -11,7 +11,6 @@ SKILLS_SRC="$SCRIPT_DIR/skills" RULES_SRC="$SCRIPT_DIR/rules" TEAM_YAML="$SCRIPT_DIR/TEAM.yaml" AGENTS_DST="$CLAUDE_DIR/agents" -SKILLS_DST="$CLAUDE_DIR/skills" RULES_DST="$CLAUDE_DIR/rules" CLAUDE_MD_SRC="$SCRIPT_DIR/claude/CLAUDE.md" CLAUDE_MD_DST="$CLAUDE_DIR/CLAUDE.md" @@ -31,12 +30,15 @@ echo "Source: $SCRIPT_DIR" echo "Target: $CLAUDE_DIR" echo "" -# Pre-flight: require generated claude/ output before proceeding -if [ ! -d "$SCRIPT_DIR/claude" ]; then - echo "Error: claude/ not found. Run ./generate.sh first." +# Pre-flight: build fresh generated outputs before proceeding. +if [ ! -f "$SCRIPT_DIR/generate.sh" ]; then + echo "Error: generate.sh not found." exit 1 fi +echo "Generating fresh artifacts before install..." +bash "$SCRIPT_DIR/generate.sh" + # Ensure ~/.claude exists mkdir -p "$CLAUDE_DIR" @@ -82,6 +84,22 @@ create_symlink() { echo "Linked: $dst -> $src" } +ensure_directory() { + local dst="$1" + local name="$2" + + if [ -L "$dst" ]; then + echo "Removing existing symlink: $dst" + rm "$dst" + elif [ -f "$dst" ]; then + local backup="${dst}.backup.$(date +%Y%m%d%H%M%S)" + echo "Backing up existing $name file to: $backup" + mv "$dst" "$backup" + fi + + mkdir -p "$dst" +} + # Symlink a single file create_file_symlink() { local src="$1" @@ -165,11 +183,95 @@ resolve_skill_dir_from_team() { printf '%s\n' "$skill_dir" } +install_team_skills_for_target() { + local target="$1" + local dst_root="$2" + local label_prefix="$3" + + ensure_directory "$dst_root" "$label_prefix skills" + + local skill_dir skill_name skill_dst skill_id skill_dir_path + local expected_skills_tmp + expected_skills_tmp="$(mktemp)" + + cleanup_skill_symlinks() { + local expected_file="$1" + local existing_path existing_name + + for existing_path in "$dst_root"/*; do + [ -e "$existing_path" ] || [ -L "$existing_path" ] || continue + [ -L "$existing_path" ] || continue + + existing_name="$(basename "$existing_path")" + if [ -s "$expected_file" ] && grep -Fxq "$existing_name" "$expected_file"; then + continue + fi + + echo "Removing stale symlink: $existing_path" + rm "$existing_path" + done + } + + if [ -f "$TEAM_YAML" ]; then + local team_skills_tmp + team_skills_tmp="$(mktemp)" + if ! list_team_skills_for_target "$target" > "$team_skills_tmp" 2>/dev/null; then + echo "Warning: TEAM.yaml exists but could not be parsed; falling back to directory-based ${label_prefix} skill install." + for skill_dir in "$SKILLS_SRC"/*/; do + skill_name="$(basename "$skill_dir")" + printf '%s\n' "$skill_name" >> "$expected_skills_tmp" + done + cleanup_skill_symlinks "$expected_skills_tmp" + for skill_dir in "$SKILLS_SRC"/*/; do + skill_name="$(basename "$skill_dir")" + create_symlink "$skill_dir" "$dst_root/$skill_name" "${label_prefix} skill: $skill_name" + done + rm -f "$team_skills_tmp" + rm -f "$expected_skills_tmp" + return + fi + + while IFS= read -r skill_id; do + [ -n "$skill_id" ] || continue + printf '%s\n' "$skill_id" >> "$expected_skills_tmp" + done < "$team_skills_tmp" + + cleanup_skill_symlinks "$expected_skills_tmp" + + while IFS= read -r skill_id; do + [ -n "$skill_id" ] || continue + skill_dir_path="$(resolve_skill_dir_from_team "$skill_id" || true)" + if [ -z "$skill_dir_path" ]; then + echo "Warning: TEAM.yaml skill '$skill_id' has no valid instruction_file directory; skipping." + continue + fi + create_symlink "$skill_dir_path" "$dst_root/$skill_id" "${label_prefix} skill: $skill_id" + done < "$team_skills_tmp" + rm -f "$team_skills_tmp" + rm -f "$expected_skills_tmp" + return + fi + + for skill_dir in "$SKILLS_SRC"/*/; do + skill_name="$(basename "$skill_dir")" + printf '%s\n' "$skill_name" >> "$expected_skills_tmp" + done + + cleanup_skill_symlinks "$expected_skills_tmp" + + for skill_dir in "$SKILLS_SRC"/*/; do + skill_name="$(basename "$skill_dir")" + create_symlink "$skill_dir" "$dst_root/$skill_name" "${label_prefix} skill: $skill_name" + done + + rm -f "$expected_skills_tmp" +} + create_symlink "$AGENTS_SRC" "$AGENTS_DST" "agents" -create_symlink "$SKILLS_SRC" "$SKILLS_DST" "skills" create_symlink "$RULES_SRC" "$RULES_DST" "rules" create_file_symlink "$CLAUDE_MD_SRC" "$CLAUDE_MD_DST" "CLAUDE.md" create_file_symlink "$SETTINGS_SRC" "$SETTINGS_DST" "settings.json" +install_team_skills_for_target "claude" "$CLAUDE_DIR/skills" "claude" # Codex CLI integration (optional — only if codex/ output exists) CODEX_DIR="$HOME/.codex" @@ -181,36 +283,7 @@ if [ -d "$SCRIPT_DIR/codex" ]; then # Skills: symlink each skill directory into ~/.codex/skills/ # (Can't replace the whole directory — .system/ must remain intact) - mkdir -p "$CODEX_DIR/skills" - if [ -f "$TEAM_YAML" ]; then - team_codex_skills_tmp="$(mktemp)" - if ! list_team_skills_for_target "codex" > "$team_codex_skills_tmp" 2>/dev/null; then - echo "Warning: TEAM.yaml exists but could not be parsed; falling back to directory-based Codex skill install." - for skill_dir in "$SKILLS_SRC"/*/; do - skill_name="$(basename "$skill_dir")" - create_symlink "$skill_dir" "$CODEX_DIR/skills/$skill_name" "codex skill: $skill_name" - done - rm -f "$team_codex_skills_tmp" - else - # TEAM is authoritative when present, including the explicit zero-skills case. - while IFS= read -r skill_id; do - [ -n "$skill_id" ] || continue - skill_dir="$(resolve_skill_dir_from_team "$skill_id" || true)" - if [ -z "$skill_dir" ]; then - echo "Warning: TEAM.yaml skill '$skill_id' has no valid instruction_file directory; skipping." - continue - fi - create_symlink "$skill_dir" "$CODEX_DIR/skills/$skill_id" "codex skill: $skill_id" - done < "$team_codex_skills_tmp" - rm -f "$team_codex_skills_tmp" - fi - else - # Legacy fallback only when TEAM.yaml is absent. - for skill_dir in "$SKILLS_SRC"/*/; do - skill_name="$(basename "$skill_dir")" - create_symlink "$skill_dir" "$CODEX_DIR/skills/$skill_name" "codex skill: $skill_name" - done - fi + install_team_skills_for_target "codex" "$CODEX_DIR/skills" "codex" # Generated agents if [ -d "$SCRIPT_DIR/codex/agents" ]; then diff --git a/rules/01-session.md b/rules/01-session.md index 49f6ecb..941bb29 100644 --- a/rules/01-session.md +++ b/rules/01-session.md @@ -10,3 +10,4 @@ - Use `MEMORY.md` in that directory as the index (one line per entry pointing to a file) - Memory files use frontmatter: `name`, `description`, `type` (user/feedback/project/reference) - Commit `memory/` with the repo so memory persists across machines and sessions +- Tool-specific runtime memory (for example `.claude/agent-memory/`) is optional and does not replace `memory/` as the project source of truth diff --git a/schemas/agent-runtime.schema.json b/schemas/agent-runtime.schema.json index 1a977d6..8f0ef23 100644 --- a/schemas/agent-runtime.schema.json +++ b/schemas/agent-runtime.schema.json @@ -149,10 +149,12 @@ "on-request", "untrusted", "never" - ] + ], + "description": "Codex-only compatibility override. Prefer runtime.approval as the portable source of truth." }, "network_access": { - "type": "boolean" + "type": "boolean", + "description": "Codex-only compatibility override. Prefer runtime.network_access as the portable source of truth." } } } diff --git a/schemas/team.schema.json b/schemas/team.schema.json index 3be7545..45f7c09 100644 --- a/schemas/team.schema.json +++ b/schemas/team.schema.json @@ -367,7 +367,7 @@ "inventory_skills": { "type": "object", "additionalProperties": false, - "description": "Skill inventory for protocol v1. This schema enforces exact order, exact keys, and key/id equality for the current repository inventory.", + "description": "Skill inventory for protocol v1. This schema enforces exact order, exact keys, and key/id equality for the current repository inventory. The fixed v1 inventory does not include a separate 'project' skill entry.", "required": [ "order", "items" diff --git a/spec/agent-runtime-v1.md b/spec/agent-runtime-v1.md index 4bf333d..a172ff8 100644 --- a/spec/agent-runtime-v1.md +++ b/spec/agent-runtime-v1.md @@ -21,7 +21,7 @@ Version 1 standardizes: - portable tool classes - protected path rules - dangerous shell command prompts -- target-specific escape hatches only when the target exposes settings with no shared equivalent +- a narrow set of target-specific escape hatches for compatibility overrides Version 1 does not attempt to standardize: @@ -50,13 +50,20 @@ Version 1 does not attempt to standardize: ### `targets` -Target blocks are escape hatches, not the main schema. Use them only where a runtime exposes a knob with no shared equivalent. +Target blocks are escape hatches, not the main schema. Current target-specific fields: - `targets.claude.claude_md_excludes` -- `targets.codex.approval_policy` -- `targets.codex.network_access` +- `targets.codex.approval_policy` (optional override of derived approval) +- `targets.codex.network_access` (optional override of derived network access) + +Authority rules: + +- `runtime.approval` and `runtime.network_access` are the portable source of truth. +- Codex target fields exist for explicit compatibility overrides and should normally be omitted. +- When Codex target fields are set, they intentionally override the derived Codex value. +- In this repo, `targets.codex.approval_policy` and `targets.codex.network_access` are intentionally set so Codex runs with `approval_policy = "never"` and network enabled by default. This is a deliberate target-specific compatibility choice, not an accidental divergence. ## Adapter rules @@ -81,15 +88,16 @@ Lossiness: - `runtime.filesystem = read-only` -> `sandbox_mode = "read-only"` - `runtime.filesystem = workspace-write` -> `sandbox_mode = "workspace-write"` -- `runtime.approval = manual` -> `approval_policy = "on-request"` -- `runtime.approval = guarded-auto` -> `approval_policy = "untrusted"` -- `runtime.approval = full-auto` -> `approval_policy = "never"` +- `runtime.approval = manual` -> `approval_policy = "on-request"` (unless overridden) +- `runtime.approval = guarded-auto` -> `approval_policy = "untrusted"` (unless overridden) +- `runtime.approval = full-auto` -> `approval_policy = "never"` (unless overridden) - `runtime.network_access` -> `[sandbox_workspace_write].network_access` Lossiness: - Codex does not expose Claude-style per-tool `allow` / `deny` / `ask` pattern controls in `config.toml`. - Protected paths and dangerous command prompts are therefore only partially representable in Codex config today. +- Codex does expose coarse approval controls, including `approval_policy` and documented granular approval categories, but not the same pattern-level permission model Claude exposes. ## Compatibility contract diff --git a/spec/team-protocol-v1.md b/spec/team-protocol-v1.md index ef2c56a..a3acf58 100644 --- a/spec/team-protocol-v1.md +++ b/spec/team-protocol-v1.md @@ -79,9 +79,10 @@ Each agent entry includes metadata required for adapter generation: Each skill entry includes lightweight metadata and content reference: - `id` +- `name` - `description` - `instruction_file` -- optional target/install metadata +- target/install metadata (`applies_to`, `install_mode`) Skill prose remains in `skills/*/SKILL.md`. @@ -110,6 +111,7 @@ Current target behavior: - `codex/config.toml` - `codex/AGENTS.md` - `codex/agents/*.toml` + - `codex/skills` symlinked to the shared skill directories for relative `skills.config` references ## Validation Requirements @@ -128,6 +130,7 @@ TEAM validation enforces schema + runtime checks for: - Existing YAML frontmatter in `agents/*.md` may remain for editorial continuity, but generation does not use it for team metadata. - Output diffs that are purely formatting-related are acceptable; semantic behavior changes are not unless explicitly documented. - TEAM schema is intentionally rigid/repo-specific in v1; inventory additions/removals require schema updates in lockstep. +- Agent metadata is not fully portable across targets. Current Codex custom-agent docs cover session-style fields such as `model`, `model_reasoning_effort`, `sandbox_mode`, `mcp_servers`, and `skills.config`, but do not document per-agent equivalents for TEAM's `background`, `memory`, or `isolation` fields. ## Out of Scope