refactor(team-planex): redesign skill with inverted control and beat model

- Delete executor agent (main flow IS the executor now)
- Rewrite SKILL.md: delegated planning + inline execution
- Input accepts issues.jsonl / roadmap session from roadmap-with-file
- Single reusable planner agent via send_input (Pattern 2.3)
- Interleaved plan-execute loop with eager delegation
- Follow codex v3 conventions (decision tables, placeholders)
- Remove complexity assessment and dynamic splitting
This commit is contained in:
catlog22
2026-03-01 15:06:06 +08:00
parent 1f859ae4b9
commit 8799a9c2fd
2 changed files with 425 additions and 393 deletions

View File

@@ -1,261 +1,503 @@
---
name: team-planex
description: |
Inline planning + delegated execution pipeline. Main flow does planning directly,
spawns Codex executor per issue immediately. All execution via Codex CLI only.
Plan-and-execute pipeline with inverted control. Accepts issues.jsonl or roadmap
session from roadmap-with-file. Delegates planning to issue-plan-agent (background),
executes inline (main flow). Interleaved plan-execute loop.
---
# Team PlanEx (Codex)
# Team PlanEx
主流程内联规划 + 委托执行。SKILL.md 自身完成规划(不再 spawn planner agent每完成一个 issue 的 solution 后立即 spawn executor agent 并行实现,无需等待所有规划完成。
接收 `issues.jsonl` 或 roadmap session 作为输入,委托规划 + 内联执行。Spawn issue-plan-agent 后台规划下一个 issue主流程内联执行当前已规划的 issue。交替循环规划 → 执行 → 规划下一个 → 执行 → 直到所有 issue 完成。
## Architecture
```
┌────────────────────────────────────────┐
│ SKILL.md (主流程 = 规划 + 节拍控制) │
│ │
│ Phase 1: 解析输入 + 初始化 session │
│ Phase 2: 逐 issue 规划循环 (内联)
│ ├── issue-plan → 写 solution artifact
│ ├── spawn executor agent ────────────┼──> [executor] 实现
└── continue (不等 executor)
Phase 3: 等待所有 executors
Phase 4: 汇总报告
└────────────────────────────────────────┘
┌────────────────────────────────────────────────────
│ SKILL.md (主流程 = 内联执行 + 循环控制)
│ Phase 1: 加载 issues.jsonl / roadmap session
│ Phase 2: 规划-执行交替循环
│ ├── 取下一个未规划 issue → spawn planner (bg)
│ ├── 取下一个已规划 issue → 内联执行 │
│ ├── 内联实现 (Read/Edit/Write/Bash)
│ ├── 验证测试 + 自修复 (max 3 retries)
│ └── git commit
│ └── 循环直到所有 issue 规划+执行完毕 │
│ Phase 3: 汇总报告 │
└────────────────────────────────────────────────────┘
Planner (single reusable agent, background):
issue-plan-agent spawn once → send_input per issue → write solution JSON → write .ready marker
```
## Beat Model (Interleaved Plan-Execute Loop)
```
Interleaved Loop (Phase 2, single planner reused via send_input):
═══════════════════════════════════════════════════════════════
Beat 1 Beat 2 Beat 3
| | |
spawn+plan(A) send_input(C) (drain)
↓ ↓ ↓
poll → A.ready poll → B.ready poll → C.ready
↓ ↓ ↓
exec(A) exec(B) exec(C)
↓ ↓ ↓
send_input(B) send_input(done) done
(eager delegate) (all delegated)
═══════════════════════════════════════════════════════════════
Planner timeline (never idle between issues):
─────────────────────────────────────────────────────────────
Planner: ├─plan(A)─┤├─plan(B)─┤├─plan(C)─┤ done
Main: 2a(spawn) ├─exec(A)──┤├─exec(B)──┤├─exec(C)──┤
^2f→send(B) ^2f→send(C)
─────────────────────────────────────────────────────────────
Single Beat Detail:
───────────────────────────────────────────────────────────────
1. Delegate next 2. Poll ready 3. Execute 4. Verify + commit
(2a or 2f eager) solutions inline + eager delegate
| | | |
send_input(bg) Glob(*.ready) Read/Edit/Write test → commit
│ → send_input(next)
wait if none ready
───────────────────────────────────────────────────────────────
```
---
## Agent Registry
| Agent | Role File | Responsibility |
|-------|-----------|----------------|
| `executor` | `~/.codex/agents/planex-executor.md` | Codex CLI implementation per issue |
| Agent | Role File | Responsibility | Pattern |
|-------|-----------|----------------|---------|
| `issue-plan-agent` | `~/.codex/agents/issue-plan-agent.md` | Explore codebase + generate solutions, single instance reused via send_input | 2.3 Deep Interaction |
> Executor agent must be deployed to `~/.codex/agents/` before use.
> Source: `.codex/skills/team-planex/agents/`
> **COMPACT PROTECTION**: Agent files are execution documents. When context compression occurs and agent instructions are reduced to summaries, **you MUST immediately `Read` the corresponding agent.md to reload before continuing execution**.
---
## Input Parsing
## Subagent API Reference
Supported input types (parse from `$ARGUMENTS`):
| Type | Detection | Handler |
|------|-----------|---------|
| Issue IDs | `ISS-\d{8}-\d{6}` regex | Use directly for planning |
| Text | `--text '...'` flag | Create issue(s) first via CLI |
| Plan file | `--plan <path>` flag | Read file, parse phases, batch create issues |
### Issue Creation (when needed)
For `--text` input:
```bash
ccw issue create --data '{"title":"<title>","description":"<description>"}' --json
```
For `--plan` input:
- Match `## Phase N: Title`, `## Step N: Title`, or `### N. Title`
- Each match → one issue (title + description from section content)
- Fallback: no structure found → entire file as single issue
---
## Session Setup
Before processing issues, initialize session directory:
### spawn_agent
```javascript
const slug = toSlug(inputDescription).slice(0, 20)
const date = new Date().toISOString().slice(0, 10).replace(/-/g, '')
const sessionDir = `.workflow/.team/PEX-${slug}-${date}`
const artifactsDir = `${sessionDir}/artifacts/solutions`
Bash(`mkdir -p "${artifactsDir}"`)
Write({
file_path: `${sessionDir}/team-session.json`,
content: JSON.stringify({
session_id: `PEX-${slug}-${date}`,
input_type: inputType,
input: rawInput,
status: "running",
started_at: new Date().toISOString(),
executors: []
}, null, 2)
})
```
---
## Phase 1: Parse Input + Initialize
1. Parse `$ARGUMENTS` to determine input type
2. Create issues if needed (--text / --plan)
3. Collect all issue IDs
4. Initialize session directory
---
## Phase 2: Inline Planning Loop
For each issue, execute planning inline (no planner agent):
### 2a. Generate Solution via issue-plan-agent
```javascript
const planAgent = spawn_agent({
const agentId = spawn_agent({
message: `
## TASK ASSIGNMENT
### MANDATORY FIRST STEPS (Agent Execute)
1. **Read role definition**: ~/.codex/agents/issue-plan-agent.md (MUST read first)
2. Read: .workflow/project-tech.json
3. Read: .workflow/project-guidelines.json
---
issue_ids: ["${issueId}"]
project_root: "${projectRoot}"
## Requirements
- Generate solution for this issue
- Auto-bind single solution
- Output solution JSON when complete
${taskContext}
`
})
const result = wait({ ids: [planAgent], timeout_ms: 600000 })
close_agent({ id: planAgent })
```
### 2b. Write Solution Artifact
### wait
```javascript
const solution = parseSolution(result)
Write({
file_path: `${artifactsDir}/${issueId}.json`,
content: JSON.stringify({
session_id: sessionId,
issue_id: issueId,
solution: solution,
planned_at: new Date().toISOString()
}, null, 2)
const result = wait({
ids: [agentId],
timeout_ms: 600000 // 10 minutes
})
```
### 2c. Spawn Executor Immediately
### send_input
Continue interaction with active agent (reuse for next issue).
```javascript
const executorId = spawn_agent({
send_input({
id: agentId,
message: `
## NEXT ISSUE
issue_ids: ["<nextIssueId>"]
## Output Requirements
1. Generate solution for this issue
2. Write solution JSON to: <artifactsDir>/<nextIssueId>.json
3. Write ready marker to: <artifactsDir>/<nextIssueId>.ready
`
})
```
### close_agent
```javascript
close_agent({ id: agentId })
```
---
## Input
Accepts output from `roadmap-with-file` or direct `issues.jsonl` path.
Supported input forms (parse from `$ARGUMENTS`):
| Form | Detection | Example |
|------|-----------|---------|
| Roadmap session | `RMAP-` prefix or `--session` flag | `team-planex --session RMAP-auth-20260301` |
| Issues JSONL path | `.jsonl` extension | `team-planex .workflow/issues/issues.jsonl` |
| Issue IDs | `ISS-\d{8}-\d{3,6}` regex | `team-planex ISS-20260301-001 ISS-20260301-002` |
### Input Resolution
| Input Form | Resolution |
|------------|------------|
| `--session RMAP-*` | Read `.workflow/.roadmap/<sessionId>/roadmap.md` → extract issue IDs from Roadmap table → load issue data from `.workflow/issues/issues.jsonl` |
| `.jsonl` path | Read file → parse each line as JSON → collect all issues |
| Issue IDs | Use directly → fetch details via `ccw issue status <id> --json` |
### Issue Record Fields Used
| Field | Usage |
|-------|-------|
| `id` | Issue identifier for planning and execution |
| `title` | Commit message and reporting |
| `status` | Skip if already `completed` |
| `tags` | Wave ordering: `wave-1` before `wave-2` |
| `extended_context.notes.depends_on_issues` | Execution ordering |
### Wave Ordering
Issues are sorted by wave tag for execution order:
| Priority | Rule |
|----------|------|
| 1 | Lower wave number first (`wave-1` before `wave-2`) |
| 2 | Within same wave: issues without `depends_on_issues` first |
| 3 | Within same wave + no deps: original order from JSONL |
---
## Session Setup
Initialize session directory before processing:
| Item | Value |
|------|-------|
| Slug | `toSlug(<first issue title>)` truncated to 20 chars |
| Date | `YYYYMMDD` format |
| Session dir | `.workflow/.team/PEX-<slug>-<date>` |
| Solutions dir | `<sessionDir>/artifacts/solutions` |
Create directories:
```bash
mkdir -p "<sessionDir>/artifacts/solutions"
```
Write `<sessionDir>/team-session.json`:
| Field | Value |
|-------|-------|
| `session_id` | `PEX-<slug>-<date>` |
| `input_type` | `roadmap` / `jsonl` / `issue_ids` |
| `source_session` | Roadmap session ID (if applicable) |
| `issue_ids` | Array of all issue IDs to process (wave-sorted) |
| `status` | `"running"` |
| `started_at` | ISO timestamp |
---
## Phase 1: Load Issues + Initialize
1. Parse `$ARGUMENTS` to determine input form
2. Resolve issues (see Input Resolution table)
3. Filter out issues with `status: completed`
4. Sort by wave ordering
5. Collect into `<issueQueue>` (ordered list of issue IDs to process)
6. Initialize session directory and `team-session.json`
7. Set `<plannedSet>` = {} , `<executedSet>` = {} , `<plannerAgent>` = null (single reusable planner)
---
## Phase 2: Plan-Execute Loop
Interleaved loop that keeps planner agent busy at all times. Each beat: (1) delegate next issue to planner if idle, (2) poll for ready solutions, (3) execute inline, (4) after execution completes, immediately delegate next issue to planner before polling again.
### Loop Entry
Set `<queueIndex>` = 0 (pointer into `<issueQueue>`).
### 2a. Delegate Next Issue to Planner
Single planner agent is spawned once and reused via `send_input` for subsequent issues.
| Condition | Action |
|-----------|--------|
| `<plannerAgent>` is null AND `<queueIndex>` < `<issueQueue>.length` | Spawn planner (first issue), advance `<queueIndex>` |
| `<plannerAgent>` exists, idle AND `<queueIndex>` < `<issueQueue>.length` | `send_input` next issue, advance `<queueIndex>` |
| `<plannerAgent>` is busy | Skip (wait for current planning to finish) |
| `<queueIndex>` >= `<issueQueue>.length` | No more issues to plan |
**First issue — spawn planner**:
```javascript
const plannerAgent = spawn_agent({
message: `
## TASK ASSIGNMENT
### MANDATORY FIRST STEPS (Agent Execute)
1. **Read role definition**: ~/.codex/agents/planex-executor.md (MUST read first)
1. **Read role definition**: ~/.codex/agents/issue-plan-agent.md (MUST read first)
2. Read: .workflow/project-tech.json
3. Read: .workflow/project-guidelines.json
---
## Issue
Issue ID: ${issueId}
Solution file: ${artifactsDir}/${issueId}.json
Session: ${sessionDir}
issue_ids: ["<issueId>"]
project_root: "<projectRoot>"
## Execution
Load solution from file → implement via Codex CLI → verify tests → commit → report.
## Output Requirements
1. Generate solution for this issue
2. Write solution JSON to: <artifactsDir>/<issueId>.json
3. Write ready marker to: <artifactsDir>/<issueId>.ready
- Marker content: {"issue_id":"<issueId>","task_count":<task_count>,"file_count":<file_count>}
## Multi-Issue Mode
You will receive additional issues via follow-up messages. After completing each issue,
output results and wait for next instruction.
`
})
executorIds.push(executorId)
executorIssueMap[executorId] = issueId
```
### 2d. Continue to Next Issue
**Subsequent issues — send_input**:
Do NOT wait for executor. Proceed to next issue immediately.
```javascript
send_input({
id: plannerAgent,
message: `
## NEXT ISSUE
issue_ids: ["<nextIssueId>"]
## Output Requirements
1. Generate solution for this issue
2. Write solution JSON to: <artifactsDir>/<nextIssueId>.json
3. Write ready marker to: <artifactsDir>/<nextIssueId>.ready
- Marker content: {"issue_id":"<nextIssueId>","task_count":<task_count>,"file_count":<file_count>}
`
})
```
Record `<planningIssueId>` = current issue ID.
### 2b. Poll for Ready Solutions
Poll `<artifactsDir>/*.ready` using Glob.
| Condition | Action |
|-----------|--------|
| New `.ready` found (not in `<executedSet>`) | Load `<issueId>.json` solution → proceed to 2c |
| `<plannerAgent>` busy, no `.ready` yet | Check planner: `wait({ ids: [<plannerAgent>], timeout_ms: 30000 })` |
| Planner finished current issue | Mark planner idle, re-poll |
| Planner timed out (30s wait) | Re-poll (planner still working) |
| No `.ready`, planner idle, all issues delegated | Exit loop → Phase 3 |
| Idle >5 minutes total | Exit loop → Phase 3 |
### 2c. Inline Execution
Main flow implements the solution directly. For each task in `solution.tasks`, ordered by `depends_on` sequence:
| Step | Action | Tool |
|------|--------|------|
| 1. Read context | Read all files referenced in current task | Read |
| 2. Identify patterns | Note imports, naming conventions, existing structure | — (inline reasoning) |
| 3. Apply changes | Modify existing files or create new files | Edit (prefer) / Write (new files) |
| 4. Build check | Run project build command if available | Bash |
Build verification:
```bash
npm run build 2>&1 || echo BUILD_FAILED
```
| Build Result | Action |
|--------------|--------|
| Success | Proceed to 2d |
| Failure | Analyze error → fix source → rebuild (max 3 retries) |
| No build command | Skip, proceed to 2d |
### 2d. Verify Tests
Detect test command:
| Priority | Detection |
|----------|-----------|
| 1 | `package.json``scripts.test` |
| 2 | `package.json``scripts.test:unit` |
| 3 | `pytest.ini` / `setup.cfg` (Python) |
| 4 | `Makefile` test target |
Run tests. If tests fail → self-repair loop:
| Attempt | Action |
|---------|--------|
| 13 | Analyze test output → diagnose → fix source code → re-run tests |
| After 3 | Mark issue as failed, log to `<sessionDir>/errors.json`, continue |
### 2e. Git Commit
Stage and commit changes for this issue:
```bash
git add -A
git commit -m "feat(<issueId>): <solution-title>"
```
| Outcome | Action |
|---------|--------|
| Commit succeeds | Record commit hash |
| Commit fails (nothing to commit) | Warn, continue |
| Pre-commit hook fails | Attempt fix once, then warn and continue |
### 2f. Update Status + Eagerly Delegate Next
Update issue status:
```bash
ccw issue update <issueId> --status completed
```
Add `<issueId>` to `<executedSet>`.
**Eager delegation**: Immediately check planner state and delegate next issue before returning to poll:
| Planner State | Action |
|---------------|--------|
| Idle AND more issues in queue | `send_input` next issue → advance `<queueIndex>` |
| Busy (still planning) | Skip — planner already working |
| All issues delegated | Skip — nothing to delegate |
This ensures planner is never idle between beats. Return to 2b for next beat.
---
## Phase 3: Wait All Executors
## Phase 3: Report
### 3a. Cleanup
Close the planner agent. Ignore cleanup failures.
```javascript
if (executorIds.length > 0) {
const execResults = wait({ ids: executorIds, timeout_ms: 1800000 })
if (plannerAgent) {
try { close_agent({ id: plannerAgent }) } catch {}
}
```
if (execResults.timed_out) {
const pending = executorIds.filter(id => !execResults.status[id]?.completed)
if (pending.length > 0) {
const pendingIssues = pending.map(id => executorIssueMap[id])
Write({
file_path: `${sessionDir}/pending-executors.json`,
content: JSON.stringify({ pending_issues: pendingIssues, executor_ids: pending }, null, 2)
})
}
}
### 3b. Generate Report
// Collect summaries
const summaries = executorIds.map(id => ({
issue_id: executorIssueMap[id],
status: execResults.status[id]?.completed ? 'completed' : 'timeout',
output: execResults.status[id]?.completed ?? null
}))
Update `<sessionDir>/team-session.json`:
// Cleanup
executorIds.forEach(id => {
try { close_agent({ id }) } catch { /* already closed */ }
})
| Field | Value |
|-------|-------|
| `status` | `"completed"` |
| `completed_at` | ISO timestamp |
| `results.total` | Total issues in queue |
| `results.completed` | Count in `<executedSet>` |
| `results.failed` | Count of failed issues |
Output summary:
```
## Pipeline Complete
**Total issues**: <total>
**Completed**: <completed>
**Failed**: <failed>
<per-issue status list>
Session: <sessionDir>
```
---
## File-Based Coordination Protocol
| File | Writer | Reader | Purpose |
|------|--------|--------|---------|
| `<artifactsDir>/<issueId>.json` | planner | main flow | Solution data |
| `<artifactsDir>/<issueId>.ready` | planner | main flow | Atomicity signal |
### Ready Marker Format
```json
{
"issue_id": "<issueId>",
"task_count": "<task_count>",
"file_count": "<file_count>"
}
```
---
## Phase 4: Report
## Session Directory
```javascript
const completed = summaries.filter(s => s.status === 'completed').length
const failed = summaries.filter(s => s.status === 'timeout').length
// Update session
Write({
file_path: `${sessionDir}/team-session.json`,
content: JSON.stringify({
...session,
status: "completed",
completed_at: new Date().toISOString(),
results: { total: executorIds.length, completed, failed }
}, null, 2)
})
return `
## Pipeline Complete
**Total issues**: ${executorIds.length}
**Completed**: ${completed}
**Timed out**: ${failed}
${summaries.map(s => `- ${s.issue_id}: ${s.status}`).join('\n')}
Session: ${sessionDir}
`
```
.workflow/.team/PEX-<slug>-<date>/
├── team-session.json
├── artifacts/
│ └── solutions/
│ ├── <issueId>.json
│ └── <issueId>.ready
└── errors.json
```
---
## User Commands
## Lifecycle Management
During execution, the user may issue:
### Timeout Protocol
| Command | Action |
|---------|--------|
| `check` / `status` | Show executor progress summary |
| `resume` / `continue` | Urge stalled executor |
| Phase | Default Timeout | On Timeout |
|-------|-----------------|------------|
| Phase 1 (Load) | 60s | Report error, stop |
| Phase 2 (Planner wait) | 600s per issue | Skip issue, write `.error` marker |
| Phase 2 (Execution) | No timeout | Self-repair up to 3 retries |
| Phase 2 (Loop idle) | 5 min total idle | Break loop → Phase 3 |
### Cleanup Protocol
At workflow end, close the planner agent:
```javascript
if (plannerAgent) {
try { close_agent({ id: plannerAgent }) } catch { /* already closed */ }
}
```
---
## Error Handling
| Scenario | Resolution |
|----------|------------|
| issue-plan-agent timeout (>10 min) | Skip issue, log error, continue to next |
| issue-plan-agent failure | Retry once, then skip with error log |
| Solution file not written | Executor reports error, logs to `${sessionDir}/errors.json` |
| Executor (Codex CLI) failure | Executor handles resume; logs CLI resume command |
| No issues to process | Report error: no issues found |
| Phase | Scenario | Resolution |
|-------|----------|------------|
| 1 | Invalid input (no issues, bad JSONL) | Report error, stop |
| 1 | Roadmap session not found | Report error, stop |
| 1 | Issue fetch fails | Retry once, then skip issue |
| 2 | Planner spawn fails | Retry once, then skip issue |
| 2 | issue-plan-agent timeout (>10 min) | Skip issue, write `.error` marker, continue |
| 2 | Solution file corrupt / unreadable | Skip, log to `errors.json`, continue |
| 2 | Implementation error | Self-repair up to 3 retries per task |
| 2 | Tests failing after 3 retries | Mark issue failed, log, continue |
| 2 | Git commit fails | Warn, mark completed anyway |
| 2 | Polling idle >5 minutes | Break loop → Phase 3 |
| 3 | Agent cleanup fails | Ignore |
---
## User Commands
| Command | Action |
|---------|--------|
| `check` / `status` | Show progress: planned / executing / completed / failed counts |
| `resume` / `continue` | Re-enter loop from Phase 2 |