mirror of
https://github.com/ksyasuda/dotfiles.git
synced 2026-07-23 16:49:46 -07:00
update
This commit is contained in:
@@ -0,0 +1,105 @@
|
||||
---
|
||||
name: delegate
|
||||
description: Delegate self-contained tasks to Claude or Codex subagents run as fresh CLI processes. Use proactively, without being asked, whenever work can be handed off — parallel research or code searches, bounded implementation chunks, independent reviews, second implementations for comparison. Pick the provider (Claude, Codex, or both) and tier that best fit each task. Not for consultations the user explicitly routed to a specific peer (use peer-consult for that).
|
||||
---
|
||||
|
||||
# Delegate
|
||||
|
||||
Hand a self-contained task to a fresh Claude or Codex subagent and get back a
|
||||
structured report. Each invocation is one subagent; run several in parallel for
|
||||
independent tasks.
|
||||
|
||||
## When to delegate
|
||||
|
||||
- Research or code searches whose details you don't need in your own context.
|
||||
- Bounded implementation chunks that can be specified up front and verified.
|
||||
- Independent review passes over a diff, file, or subsystem.
|
||||
- Getting two independent takes by sending the same task to both providers.
|
||||
|
||||
Do not delegate work that needs mid-task input from you or the user, or tasks
|
||||
so small that writing the brief costs more than doing the work.
|
||||
|
||||
## Route the task
|
||||
|
||||
Classify the smallest adequate tier:
|
||||
|
||||
| Tier | Signals | Codex | Claude |
|
||||
|---|---|---|---|
|
||||
| `quick` | Narrow, bounded, one component | `gpt-5.6-terra`, medium | `claude-sonnet-4-6`, medium |
|
||||
| `quick-context` | Still bounded; more context or nuance | `gpt-5.6-terra`, high | `claude-sonnet-5`, high |
|
||||
| `standard` | Multi-file feature, ordinary design/debugging, meaningful trade-offs | `gpt-5.6-sol`, medium | `claude-opus-4-8`, medium |
|
||||
| `deep` | Architecture, concurrency, security, broad ambiguity, high-impact choice | `gpt-5.6-sol`, high | `claude-opus-4-8`, high |
|
||||
|
||||
Pick the provider by judgment:
|
||||
|
||||
- Either provider handles general coding, research, and review well; when the
|
||||
task has no special pull, prefer the provider you are NOT running as, so the
|
||||
subagent brings an independent perspective.
|
||||
- Use **both** (two invocations, same task) when you want independent
|
||||
perspectives on a risky or ambiguous problem, or when splitting parallel
|
||||
work across providers to avoid rate-limit contention.
|
||||
- Honor an explicit user model preference only if it is in the allowed list:
|
||||
Codex `gpt-5.6-terra`/`gpt-5.6-sol`; Claude
|
||||
`claude-sonnet-4-6`/`claude-sonnet-5`/`claude-opus-4-8`. Never silently
|
||||
substitute another model.
|
||||
|
||||
Pick the mode:
|
||||
|
||||
- `read` (default): research, searches, reviews, second opinions. Subagent
|
||||
cannot modify files.
|
||||
- `write`: implementation and refactors. Subagent may edit files and run
|
||||
commands inside the repo; it will not commit or push unless the task says to.
|
||||
|
||||
## Write the brief
|
||||
|
||||
The subagent starts with zero context. Write a self-contained task containing:
|
||||
|
||||
- The goal and definition of done.
|
||||
- Relevant paths, symbols, error messages, or raw diff/plan text.
|
||||
- Constraints (style, APIs to use or avoid, files off-limits).
|
||||
- For `write` mode: how to verify (build/test commands).
|
||||
|
||||
Pass raw evidence; do not tell the subagent the answer you expect.
|
||||
|
||||
## Run it
|
||||
|
||||
Resolve `<skill-dir>` to the directory containing this `SKILL.md`. Send the
|
||||
task on stdin; do not interpolate it into the shell command.
|
||||
|
||||
```bash
|
||||
python3 <skill-dir>/scripts/delegate.py \
|
||||
--provider codex \
|
||||
--tier standard \
|
||||
--mode read \
|
||||
--repo "$PWD" <<'TASK'
|
||||
<self-contained task brief>
|
||||
TASK
|
||||
```
|
||||
|
||||
Flags:
|
||||
|
||||
- `--provider claude|codex` (required)
|
||||
- `--tier quick|quick-context|standard|deep` (default `standard`)
|
||||
- `--mode read|write` (default `read`)
|
||||
- `--model <approved-model>` for an explicit allowed override
|
||||
- `--repo <dir>` (default cwd)
|
||||
- `--timeout <seconds>` (default 1800)
|
||||
- `--dry-run` to inspect routing and command construction without running
|
||||
|
||||
Parallel delegation: launch each invocation as a separate background shell
|
||||
command, then collect the outputs. Never point two `write`-mode subagents at
|
||||
overlapping files; split by file/directory or run them sequentially.
|
||||
|
||||
## Handle the result
|
||||
|
||||
The report ends with Summary / Details / Files changed / Verification / Open
|
||||
questions sections. Then:
|
||||
|
||||
- Treat it as a subagent's claim, not ground truth: spot-check load-bearing
|
||||
findings, and for `write` mode review the diff (`git diff`) and re-run
|
||||
verification before building on it.
|
||||
- Relay the outcome to the user in your own words; credit which provider/model
|
||||
produced it when it matters.
|
||||
- On failure, report the provider, model, and exact error. Retry once with the
|
||||
same route if transient; escalate tier or switch provider only deliberately,
|
||||
and say you did.
|
||||
Binary file not shown.
Executable
+270
@@ -0,0 +1,270 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Delegate a self-contained task to a Claude or Codex subagent."""
|
||||
|
||||
import argparse
|
||||
import os
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
from dataclasses import dataclass
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
ALLOWED_MODELS = {
|
||||
"codex": frozenset({"gpt-5.6-terra", "gpt-5.6-sol"}),
|
||||
"claude": frozenset(
|
||||
{"claude-sonnet-4-6", "claude-sonnet-5", "claude-opus-4-8"}
|
||||
),
|
||||
}
|
||||
|
||||
# tier -> provider -> (model, effort)
|
||||
ROUTES = {
|
||||
"quick": {
|
||||
"codex": ("gpt-5.6-terra", "medium"),
|
||||
"claude": ("claude-sonnet-4-6", "medium"),
|
||||
},
|
||||
"quick-context": {
|
||||
"codex": ("gpt-5.6-terra", "high"),
|
||||
"claude": ("claude-sonnet-5", "high"),
|
||||
},
|
||||
"standard": {
|
||||
"codex": ("gpt-5.6-sol", "medium"),
|
||||
"claude": ("claude-opus-4-8", "medium"),
|
||||
},
|
||||
"deep": {
|
||||
"codex": ("gpt-5.6-sol", "high"),
|
||||
"claude": ("claude-opus-4-8", "high"),
|
||||
},
|
||||
}
|
||||
|
||||
READ_ONLY_RULES = """\
|
||||
Hard constraints:
|
||||
- You are in read-only mode. Do not modify, create, rename, or delete files.
|
||||
- Do not commit, branch, push, or contact external systems that change state.
|
||||
- Base claims on repository evidence. Cite file paths and line numbers."""
|
||||
|
||||
WRITE_RULES = """\
|
||||
Hard constraints:
|
||||
- Work only inside the given repository directory.
|
||||
- Do not commit, branch, push, or open pull requests unless the task says to.
|
||||
- Do not delete or rename files the task does not cover; if something looks
|
||||
wrong or unexpected, stop and report instead of guessing.
|
||||
- Verify your work (build, tests, or a targeted check) when feasible."""
|
||||
|
||||
REPORT_FORMAT = """\
|
||||
End your reply with a report containing these sections:
|
||||
- Summary: what you did or found, in a few sentences.
|
||||
- Details: key evidence, decisions, or findings, with file:line references.
|
||||
- Files changed: list each changed file, or "none".
|
||||
- Verification: what you ran and the outcome, or "not verified" and why.
|
||||
- Open questions: anything unresolved the caller must decide, or "none"."""
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class Route:
|
||||
provider: str
|
||||
model: str
|
||||
effort: str
|
||||
|
||||
|
||||
def select_route(provider: str, tier: str) -> Route:
|
||||
if tier not in ROUTES:
|
||||
raise ValueError(f"Unknown tier: {tier}")
|
||||
if provider not in ("claude", "codex"):
|
||||
raise ValueError(f"Unknown provider: {provider}")
|
||||
model, effort = ROUTES[tier][provider]
|
||||
return Route(provider, model, effort)
|
||||
|
||||
|
||||
def apply_model_override(route: Route, model: str) -> Route:
|
||||
if model not in ALLOWED_MODELS[route.provider]:
|
||||
raise ValueError(f"Model {model!r} is not allowed for {route.provider}")
|
||||
return Route(route.provider, model, route.effort)
|
||||
|
||||
|
||||
def build_command(
|
||||
route: Route,
|
||||
repo: Path,
|
||||
mode: str,
|
||||
output_path: Path | None = None,
|
||||
) -> list[str]:
|
||||
if route.provider == "codex":
|
||||
if output_path is None:
|
||||
raise ValueError("Codex requires an output path")
|
||||
sandbox = "read-only" if mode == "read" else "workspace-write"
|
||||
return [
|
||||
"codex",
|
||||
"exec",
|
||||
"--ignore-user-config",
|
||||
"--model",
|
||||
route.model,
|
||||
"--config",
|
||||
f'model_reasoning_effort="{route.effort}"',
|
||||
"--config",
|
||||
'approval_policy="never"',
|
||||
"--sandbox",
|
||||
sandbox,
|
||||
"--ephemeral",
|
||||
"--skip-git-repo-check",
|
||||
"--output-last-message",
|
||||
str(output_path),
|
||||
"--cd",
|
||||
str(repo),
|
||||
"-",
|
||||
]
|
||||
|
||||
command = [
|
||||
"claude",
|
||||
"--print",
|
||||
"--model",
|
||||
route.model,
|
||||
"--effort",
|
||||
route.effort,
|
||||
"--disable-slash-commands",
|
||||
"--no-session-persistence",
|
||||
]
|
||||
if mode == "read":
|
||||
command += [
|
||||
"--permission-mode",
|
||||
"plan",
|
||||
"--tools",
|
||||
"Read,Glob,Grep",
|
||||
]
|
||||
else:
|
||||
command += [
|
||||
"--permission-mode",
|
||||
"acceptEdits",
|
||||
"--tools",
|
||||
"Read,Glob,Grep,Edit,Write,Bash",
|
||||
"--allowedTools",
|
||||
"Edit,Write,Bash",
|
||||
]
|
||||
return command
|
||||
|
||||
|
||||
def build_prompt(task: str, route: Route, mode: str) -> str:
|
||||
rules = READ_ONLY_RULES if mode == "read" else WRITE_RULES
|
||||
return f"""You are a {route.provider} subagent completing a delegated task \
|
||||
inside the current repository. Work independently; the caller cannot answer \
|
||||
questions mid-task. If the task is ambiguous, choose the safest reasonable \
|
||||
interpretation and note the choice in your report.
|
||||
|
||||
{rules}
|
||||
- Do not delegate further or invoke Claude or Codex recursively.
|
||||
|
||||
{REPORT_FORMAT}
|
||||
|
||||
Task:
|
||||
{task.strip()}
|
||||
"""
|
||||
|
||||
|
||||
def run_delegation(
|
||||
route: Route,
|
||||
repo: Path,
|
||||
prompt: str,
|
||||
mode: str,
|
||||
*,
|
||||
timeout: int = 1800,
|
||||
) -> str:
|
||||
if shutil.which(route.provider) is None:
|
||||
raise RuntimeError(f"Required CLI is not installed: {route.provider}")
|
||||
|
||||
with tempfile.TemporaryDirectory(prefix="delegate-") as temp_dir:
|
||||
output_path = (
|
||||
Path(temp_dir) / "report.md" if route.provider == "codex" else None
|
||||
)
|
||||
command = build_command(route, repo, mode, output_path)
|
||||
try:
|
||||
result = subprocess.run(
|
||||
command,
|
||||
cwd=repo,
|
||||
input=prompt,
|
||||
text=True,
|
||||
capture_output=True,
|
||||
timeout=timeout,
|
||||
check=False,
|
||||
)
|
||||
except subprocess.TimeoutExpired as error:
|
||||
raise RuntimeError(
|
||||
f"{route.provider} delegation timed out after {timeout}s"
|
||||
) from error
|
||||
if result.returncode != 0:
|
||||
detail = (result.stderr or result.stdout).strip() or "no error output"
|
||||
raise RuntimeError(
|
||||
f"{route.provider} delegation failed ({result.returncode}): {detail}"
|
||||
)
|
||||
if route.provider == "codex":
|
||||
if output_path is None or not output_path.is_file():
|
||||
raise RuntimeError("Codex did not produce a report")
|
||||
report = output_path.read_text().strip()
|
||||
else:
|
||||
report = result.stdout.strip()
|
||||
if not report:
|
||||
raise RuntimeError(f"{route.provider} returned an empty report")
|
||||
return report
|
||||
|
||||
|
||||
def parse_args(argv: list[str] | None = None) -> argparse.Namespace:
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Delegate a self-contained task to a Claude or Codex subagent."
|
||||
)
|
||||
parser.add_argument("--provider", choices=("claude", "codex"), required=True)
|
||||
parser.add_argument(
|
||||
"--tier",
|
||||
choices=("quick", "quick-context", "standard", "deep"),
|
||||
default="standard",
|
||||
)
|
||||
parser.add_argument("--mode", choices=("read", "write"), default="read")
|
||||
parser.add_argument("--model", help="Approved model override")
|
||||
parser.add_argument("--repo", type=Path, default=Path.cwd())
|
||||
parser.add_argument("--timeout", type=int, default=1800)
|
||||
parser.add_argument("--dry-run", action="store_true")
|
||||
return parser.parse_args(argv)
|
||||
|
||||
|
||||
def main(argv: list[str] | None = None) -> int:
|
||||
args = parse_args(argv)
|
||||
try:
|
||||
task = sys.stdin.read().strip()
|
||||
if not task:
|
||||
raise ValueError("Task must be provided on stdin")
|
||||
repo = args.repo.expanduser().resolve()
|
||||
if not repo.is_dir():
|
||||
raise ValueError(f"Repository directory does not exist: {repo}")
|
||||
route = select_route(args.provider, args.tier)
|
||||
if args.model:
|
||||
route = apply_model_override(route, args.model)
|
||||
prompt = build_prompt(task, route, args.mode)
|
||||
if args.dry_run:
|
||||
output_path = (
|
||||
Path(tempfile.gettempdir()) / "delegate-dry-run-report.md"
|
||||
if route.provider == "codex"
|
||||
else None
|
||||
)
|
||||
command = build_command(route, repo, args.mode, output_path)
|
||||
print(f"provider: {route.provider}")
|
||||
print(f"model: {route.model}")
|
||||
print(f"effort: {route.effort}")
|
||||
print(f"mode: {args.mode}")
|
||||
print(f"command: {' '.join(command)}")
|
||||
print(f"prompt:\n{prompt}")
|
||||
return 0
|
||||
report = run_delegation(
|
||||
route,
|
||||
repo,
|
||||
prompt,
|
||||
args.mode,
|
||||
timeout=args.timeout,
|
||||
)
|
||||
print(f"[{route.provider} · {route.model} · {route.effort} · {args.mode}]")
|
||||
print(report)
|
||||
return 0
|
||||
except (OSError, RuntimeError, ValueError) as error:
|
||||
print(f"delegate: error: {error}", file=sys.stderr)
|
||||
return 2
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
Reference in New Issue
Block a user