بنقرة واحدة
epic-team
Orchestrate a Claude Code agent team to autonomously work a Linear epic across multiple repos
التثبيت باستخدام Codex أو Claude انسخ هذا Prompt والصقه في Codex أو Claude أو مساعد آخر ليراجع صفحة Skill ويثبّتها لك.
القائمة
Orchestrate a Claude Code agent team to autonomously work a Linear epic across multiple repos
التثبيت باستخدام Codex أو Claude انسخ هذا Prompt والصقه في Codex أو Claude أو مساعد آخر ليراجع صفحة Skill ويثبّتها لك.
استنادا إلى تصنيف SOC المهني
Dispatch a background worker with role-templated prompt and auto-populated collision fences
Comprehensive system health monitoring — checks agent performance, database, Kafka topics, pattern discovery, and service status across the ONEX platform
Run DoD evidence checks against a ticket contract and generate a verification receipt
Autonomous per-ticket pipeline that chains ticket-work, local-review, PR creation, CI watching, PR review loop, integration verification gate, and auto-merge into a single unattended workflow with Slack notifications and policy guardrails
Full autonomous audit-debug-fix loop for all dashboard pages — Playwright recon, parallel systematic-debug, fix, PR, Linear ticket, re-audit, iterate until clean. Supports local and cloud targets with optional post-fix redeployment.
Full post-release runtime redeploy — syncs bare clones, updates Dockerfile plugin pins, rebuilds Docker runtime, seeds Infisical, and verifies health
| name | epic-team |
| description | Orchestrate a Claude Code agent team to autonomously work a Linear epic across multiple repos |
| version | 2.0.0 |
| level | advanced |
| debug | false |
| category | workflow |
| tags | ["epic","team","multi-repo","autonomous","linear","slack"] |
| args | [{"epic_id (required)":"Linear epic ID (e.g., OMN-2000)"},{"--dry-run":"Print decomposition plan (includes unmatched reason), no spawning"},{"--force":"Pause if active tasks remain; archive state and restart"},{"--force-kill":"Combine with --force to destroy active run even with live workers"},{"--resume":"Re-enter monitoring; finalize if all tasks terminal; no-op if already done"},{"--force-unmatched":"Route unmatched tickets to omniplan as TRIAGE tasks"}] |
When invoked, your FIRST and ONLY action is to dispatch to a polymorphic-agent. Do NOT read files, run bash, or take any other action before dispatching.
Agent(
subagent_type="onex:polymorphic-agent",
description="Run epic-team for <epic_id>",
prompt="Run the epic-team skill. <full context and args>"
)
CRITICAL: subagent_type MUST be "onex:polymorphic-agent" (with the onex: prefix).
Session lifetime: The monitoring phase is alive only while this session runs. Use
/epic-team {epic_id} --resumeto re-enter after a disconnection.
Architecture note (v2.0.0): epic-team is a thin orchestrator. All business logic lives in independently-invocable composable sub-skills. epic-team's job is coordination, state, and routing — not implementation.
Decompose a Linear epic into per-repo workstreams and autonomously drive them to completion.
The team lead (this session) owns planning, dispatch, state persistence, and lifecycle
notifications. Tickets are executed by dispatching ticket-pipeline as sequential Task()
subagents directly from the team-lead session, in dependency-respecting waves.
Key constraint: Workers spawned as team members (via TeamCreate + Task(team_name=...))
go idle immediately and never process tasks. The proven working pattern is direct dispatch
from the team-lead session — see the Architecture section below.
If the epic has zero child tickets, epic-team invokes decompose-epic to create sub-tickets,
then posts a Slack LOW_RISK gate. Silence for 30 minutes = proceed.
epic-team orchestrates these independently-invocable primitives:
| Sub-Skill | Purpose | Ticket |
|---|---|---|
decompose-epic | Analyze epic → create Linear child tickets | OMN-2522 |
slack-gate | LOW_RISK / MEDIUM_RISK / HIGH_RISK human gates | OMN-2521 |
ticket-pipeline | Per-ticket pipeline (implement → review → PR → CI → merge) | — |
ticket-pipeline in turn composes:
| Sub-Skill | Purpose | Ticket |
|---|---|---|
ticket-work | Implement ticket (autonomous mode) | OMN-2526 |
local-review | Review + fix loop | — |
ci-watch | Poll CI, auto-fix failures | OMN-2523 |
pr-watch | Poll PR reviews, auto-fix comments | OMN-2524 |
auto-merge | Merge PR with HIGH_RISK Slack gate | OMN-2525 |
Each layer is independently invocable:
ticket-pipeline runs standalone without epic-teamticket-work runs standalone without ticket-pipelineci-watch runs standalone on any PRIf the epic has zero child tickets, epic-team automatically invokes the decompose-epic sub-skill to analyze the epic description and create sub-tickets, then posts a Slack LOW_RISK gate. Silence for 30 minutes means proceed with newly created tickets.
# Dry run — see decomposition without spawning agents
/epic-team OMN-2000 --dry-run
# Full run
/epic-team OMN-2000
# Resume after session disconnect
/epic-team OMN-2000 --resume
# Force restart (archive existing state; pauses if workers active)
/epic-team OMN-2000 --force
# Force restart even with active workers (dangerous)
/epic-team OMN-2000 --force --force-kill
# Route unmatched tickets to omniplan triage
/epic-team OMN-2000 --force-unmatched
epic-team OMN-XXXX
→ Fetch child tickets from Linear
→ If 0 child tickets:
→ Dispatch decompose-epic (composable, returns skill_result)
→ Read ~/.claude/skill-results/{context_id}/decompose-epic.json
→ Dispatch slack-gate (LOW_RISK, 30 min, silence=proceed)
→ Read ~/.claude/skill-results/{context_id}/slack-gate.json
→ If rejected: stop
→ Re-fetch newly created tickets
→ Assign tickets to repos via repo_manifest
→ Build dependency waves:
Wave 0: independent tickets + cross-repo Part 1 splits (run in parallel)
Wave 1: cross-repo Part 2 splits (run after Wave 0 completes)
→ For each wave: dispatch ticket-pipeline per ticket as Task() from team-lead session
→ Await all Task() calls in wave before starting next wave
→ Collect results (status, pr_url, branch) from each Task()
→ Post-wave integration check (OMN-3345): run gap cycle --no-fix per repo touched
→ GREEN/YELLOW/RED per repo → post to Slack epic thread
→ Write integration_check section to state.yaml (non-blocking — always advances)
→ Send Slack lifecycle notifications (started, ticket done, epic done)
→ Persist state to ~/.claude/epics/{epic_id}/state.yaml
When epic has 0 child tickets:
Task(
subagent_type="onex:polymorphic-agent",
description="epic-team: auto-decompose empty epic {epic_id}",
prompt="The epic {epic_id} has no child tickets.
Invoke: Skill(skill=\"onex:decompose-epic\", args=\"{epic_id}\")
Read result from ~/.claude/skill-results/{context_id}/decompose-epic.json
Report back: created_tickets (list of IDs and titles), count."
)
For each ticket in a wave, dispatch ticket-pipeline as a Task() from the team-lead session:
Task(
subagent_type="onex:polymorphic-agent",
description="epic-team: ticket-pipeline for {ticket_id} [{repo}]",
prompt="You are executing ticket {ticket_id} for epic {epic_id}.
Ticket: {ticket_id} - {title}
URL: {url}
Repo: {repo} at {repo_path}
Epic: {epic_id} Run: {run_id}
Invoke: Skill(skill=\"onex:ticket-pipeline\", args=\"{ticket_id}\")
After ticket-pipeline completes, report back:
- ticket_id: {ticket_id}
- status: (merged/failed/blocked)
- pr_url: (if available)
- branch: (branch name used)"
)
Wave parallelism: All Task() calls within a wave MUST be dispatched in the same response (same message) for true parallelism. Do NOT dispatch tickets sequentially within a wave.
Wave serialization: Wave N+1 starts only after all Task() calls from Wave N have returned.
DEPRECATED: Spawning per-repo workers via TeamCreate + Task(team_name=...) + a
WORKER_TEMPLATE is no longer used. See prompt.md for the deprecated WORKER_TEMPLATE
preserved for historical reference.
All sub-skills write their output to ~/.claude/skill-results/{context_id}/:
| Sub-Skill | Output File | Key Fields |
|---|---|---|
decompose-epic | decompose-epic.json | status, created_tickets, count |
slack-gate | slack-gate.json | status (accepted/rejected/timeout) |
ticket-pipeline | ticket-pipeline.json | status, ticket_id, pr_url |
ticket-work | ticket-work.json | status, pr_url, phase_reached |
local-review | local-review.json | status, iterations_run |
ci-watch | ci-watch.json | status, fix_cycles_used |
pr-watch | pr-watch.json | status, fix_cycles_used |
auto-merge | auto-merge.json | status, merge_commit |
Input contract: All sub-skill result files conform to ModelSkillResult from omnibase_core.models.skill.
Note: This contract reference is behavioral guidance for the LLM executing this skill. Runtime validation not yet implemented.
Load result files and check outcomes as follows:
result = ModelSkillResult.from_json(path.read_text())
# Check if the sub-skill completed successfully (success, partial, or dry_run)
if result.is_success_like:
# Proceed with next wave or action
pass
# Check for hard failure
elif result.status == EnumSkillResultStatus.FAILED:
# Record failure, apply circuit breaker (max 2 retries per ticket)
pass
# Check for blocking states
elif result.status == EnumSkillResultStatus.GATED:
# Human approval is pending — do not advance wave
pass
# Access skill-specific fields via extra dict (not direct attribute access).
# Use .get() with a default — extra may be empty on non-success paths.
created_tickets = result.extra.get("created_tickets", []) # decompose-epic result
iterations_run = result.extra.get("iterations_run", 0) # local-review result
Behaviorally significant extra_status values by sub-skill:
| Sub-Skill | extra_status | Orchestrator action |
|---|---|---|
decompose-epic | null | Normal — read extra.get("created_tickets", []) |
slack-gate | "accepted" | Silence-proceed gate passed — continue |
slack-gate | "rejected" | Gate rejected — stop (cancel orchestration) |
slack-gate | "timeout" | Gate timed out — apply configured timeout policy |
ticket-pipeline | "merged" | Ticket fully merged — record as done |
ticket-pipeline | "held" | Merge gate open — non-terminal, do not retry yet |
auto-merge | "merged" | PR merged — record wave ticket as complete |
auto-merge | "timeout" | Merge gate expired — retryable with new pipeline run |
Promotion rule for extra fields: If any orchestrator consumer (epic-team, ticket-pipeline) branches on extra["x"], that field MUST be promoted to a first-class field in ModelSkillResult. extra is a migration bridge, not a permanent schema extension mechanism.
Runtime state is persisted to ~/.claude/epics/{epic_id}/state.yaml:
epic_id: OMN-XXXX
run_id: f084b6c3
status: monitoring # queued | monitoring | done | failed
checkpoint:
schema_version: 1
last_completed_wave: 0
waves:
- wave_id: 0
tickets: [OMN-2001, OMN-2002]
status: completed # pending | in_progress | completed | failed
completed_at: "2026-03-06T..."
- wave_id: 1
tickets: [OMN-2003]
status: pending
open_prs:
OMN-2001: {pr_number: 45, repo: "omniclaude", branch: "jonah/omn-2001-..."}
failures:
OMN-2003: {class: "ci_failure_ruff", attempts: 1, last_error: "..."}
last_update_utc: "2026-03-06T..."
workers:
- repo: omniclaude
tickets: [OMN-2001, OMN-2002]
status: running # running | done | failed
ticket_status:
OMN-2001: merged
OMN-2002: running
Use --resume to re-enter monitoring from persisted state after session disconnect.
When epic has 0 child tickets:
[LOW_RISK] epic-team: Auto-decomposed OMN-XXXX
Epic had no child tickets. Created N sub-tickets:
- OMN-YYYY: [title]
- OMN-ZZZZ: [title]
...
Reply "reject" within 30 minutes to cancel. Silence = proceed with orchestration.
Invoke decompose-epic --dry-run (returns plan, no tickets created). Print plan. Do not post
Slack gate.
Ticket-to-repo assignment uses plugins/onex/skills/epic-team/repo_manifest.yaml:
MIN_TOP_SCORE: 4
repos:
- name: omniclaude
path: ~/Code/omniclaude
keywords: [hooks, skills, agents, claude, plugin, ticket-pipeline]
- name: omnibase_core
path: ~/Code/omnibase_core
keywords: [nodes, contracts, runtime, onex]
- name: omnibase_infra
path: ~/Code/omnibase_infra
keywords: [kubernetes, deploy, infra, helm]
Keyword matching is case-insensitive. Tickets with no keyword match are UNMATCHED.
Use --force-unmatched to route them to omniplan as TRIAGE tasks.
Workers create isolated git worktrees at:
~/.claude/worktrees/{epic_id}/{run_id}/{ticket_id}/
Stale worktrees are cleaned up automatically after merge when auto_cleanup_merged_worktrees: true
(default).
epic-team is a thin composition layer. It owns:
decompose-epic)~/.claude/epics/{epic_id}/state.yaml)It does NOT own:
ticket-pipeline → ticket-work)local-review)ci-watch)pr-watch)auto-merge)Direct dispatch from team-lead session is the authoritative execution pattern:
Task() per ticket in parallelWhy not per-repo workers? Workers spawned as team members via TeamCreate + Task(team_name=...)
go idle immediately (idleReason: available) and never process tasks from the task queue.
This is a confirmed behavior across multiple epic runs. The direct dispatch pattern is the
only execution model that reliably completes tickets.
| Failure Class | Symptoms | Recovery Strategy |
|---|---|---|
rate_limit | Sub-agent exits with rate limit error | Wait 60s, retry the ticket via ticket-pipeline sequentially |
context_limit | Sub-agent hits max context length mid-ticket | Spawn fresh sub-agent; previous work is in the branch |
ci_failure_uv | CI fails with lock file or uv version error | Verify CI uv version; regenerate lock with matching version |
ci_failure_ruff | CI fails with lint/format error | Run uv run ruff check --fix + uv run ruff format, recommit |
stale_branch | PR fails to merge — "main has moved" | git rebase origin/main, re-push, re-enable auto-merge |
wrong_repo | Ticket worked in wrong repo | Look up target repo in Linear ticket repo field; re-dispatch |
blocker_unresolved | Ticket blocked by another in-progress ticket | Move to end of queue; complete blocking ticket first |
pr_template_blocked | Mergeability gate rejected — missing PR body sections | Update PR body with required sections (Summary/Risk/Test Evidence/Rollback), rerun gate |
unknown | Failure doesn't match above patterns | Escalate to user: ticket ID, last command output, branch state |
A failure attempt is counted when ticket-pipeline returns non-zero exit OR the summary contains an explicit failure class. Partial completions (branch exists, some steps done) count as one attempt.
prompt.md — full orchestration logic, state machine, and error handling referenceticket-pipeline skill — per-ticket pipeline invoked by workersticket-work skill — implementation phase (autonomous mode)local-review skill — review + fix loopci-watch skill (OMN-2523) — CI polling and auto-fixpr-watch skill (OMN-2524) — PR review polling and auto-fixauto-merge skill (OMN-2525) — merge gatedecompose-epic skill (OMN-2522) — empty epic auto-decomposeslack-gate skill (OMN-2521) — LOW/MEDIUM/HIGH_RISK gatesplugins/onex/skills/epic-team/repo_manifest.yaml — repo keyword mappingmcp__linear-server__*) — epic and ticket access