Decompose Epic
Dispatch Surface
Target: Agent Teams
Overview
Analyze a Linear epic's description, goals, and context to generate a set of actionable
sub-tickets. Creates each sub-ticket as a Linear child of the epic, assigns repo hints
from the repo manifest, and returns ModelSkillResult with created ticket details.
Announce at start: "I'm using the decompose-epic skill to create sub-tickets for {epic_id}."
Implements: decompose-epic skill
- Mode A (no
--repos): reads epic description, infers repo breakdown fromrepo_manifest.yaml, creates sub-tickets with one per identified work area matched to its owning repo - Mode B (
--repos omniclaude,omnibase_core,...): repos are pre-determined; creates one focused sub-ticket per repo, scoped to that repo's concerns
Usage Examples
/decompose-epic EPIC-ID
/decompose-epic EPIC-ID --dry-run
Decomposition Flow
- Fetch epic from Linear:
tracker.get_issue({epic_id}, includeRelations=true) - Read
plugins/onex/skills/epic-team/repo_manifest.yamlfor keyword-to-repo mapping - Analyze epic description + goals:
- Identify distinct workstreams (one ticket per independent deliverable)
- Keep tickets atomic: each ticket = one thing, one repo, one PR
- Assign repo hint based on keywords in the work description
- Generate title, description, requirements, and DoD for each ticket
- If
--dry-run: print plan, exit withstatus: dry_run - Create each ticket via
tracker.create_issue:parentId: epic's Linear IDteam: same team as epiclabels: ["omniclaude"] (or appropriate repo label)
- Post-decomposition: generate contracts for ALL child tickets.
For each created ticket:
a. Fetch ticket details from Linear
b. Extract DoD/acceptance criteria from description via dod_parser for ticket-specific checks.
Exception — never emit
test -f docs/plans/checks: theno-planning-docspre-commit hook inonex_change_controlforbids planning docs from existing in that repo (they live in the canonical registry'sdocs/plans/). Anycheck_valuecontainingtest -f docs/plans/ortest -f docs/plans/variants would be structurally unsatisfiable and must be omitted. Other ticket-specific acceptance criteria extracted by dod_parser are retained normally. c. Generate contract YAML (stub for non-seam, full for seam tickets) d. Validate each contract (YAML lint + schema check) before writing e. Write to$ONEX_CC_REPO_PATH/contracts/{ticket_id}.yamlf. Commit all contracts in a single batch via branch + PR:
This ensures tickets created by decompose-epic have the same contract coverage as tickets created by plan-to-tickets.cd $ONEX_CC_REPO_PATH git checkout -b auto/contracts-{epic_id} git add contracts/OMN-*.yaml git commit -m "feat: auto-generate contracts for {epic_id} decomposition ({N} tickets)" git push origin auto/contracts-{epic_id} gh pr create --title "auto: contracts for {epic_id} ({N} tickets)" \ --body "Auto-generated ticket contracts from decompose-epic" --auto - Write result and exit
Ticket Creation Contract
tracker.create_issue(
title="{ticket_title}",
team="{epic_team}",
parentId="{epic_linear_id}",
description="""
## Summary
{what_this_ticket_implements}
## Requirements
{functional_requirements}
## Definition of Done
- [ ] Implementation complete
- [ ] Tests passing
- [ ] PR merged
## Repo Hint
{repo_name}: {rationale}
""",
labels=["{repo_label}"]
)
Repo Manifest
Loaded from plugins/onex/skills/epic-team/repo_manifest.yaml:
repos:
- name: omniclaude
path: ~/Code/omniclaude
keywords: [hooks, skills, agents, claude, plugin]
- name: omnibase_core
path: ~/Code/omnibase_core
keywords: [nodes, contracts, runtime, onex]
Skill Result Output
Output contract: ModelSkillResult from omnibase_core.models.skill
Note: This contract reference is behavioral guidance for the LLM executing this skill. Runtime validation not yet implemented.
Write to: $ONEX_STATE_DIR/skill-results/{context_id}/decompose_epic.json
| Field | Value |
|---|---|
skill_name |
"decompose-epic" |
status |
One of the canonical string values: "success", "dry_run", "error" (see mapping below) |
extra_status |
Domain-specific status string (see mapping below) |
run_id |
Correlation ID |
extra |
{"epic_id": str, "created_tickets": list[{"id": str, "title": str, "repo_hint": str}], "count": int} |
Note on
context_id: Prior schema versions includedcontext_idas a top-level field. This field is not part ofModelSkillResult— it belongs to the file path convention ($ONEX_STATE_DIR/skill-results/{context_id}/decompose_epic.json). Consumers should derive context from the file path, not fromcontext_idin the result body.
Status mapping:
| Current status | Canonical status (string value) |
extra_status |
|---|---|---|
created |
"success" (EnumSkillResultStatus.SUCCESS) |
"created" |
dry_run |
"dry_run" (EnumSkillResultStatus.DRY_RUN) |
null |
error |
"error" (EnumSkillResultStatus.ERROR) |
null |
Behaviorally significant extra_status values:
"created"→ ticket-pipeline (cross-repo split path) proceeds to invoke epic-team with parent epic ID; theextra["created_tickets"]list contains the sub-ticket IDs passed to epic-teamnull(dry_run) → ticket-pipeline does not advance; decomposition plan is logged for human review only
Promotion rule for extra fields: If a field appears in 3+ producer skills, open a ticket to evaluate promotion to a first-class field. If any orchestrator consumer (epic-team, ticket-pipeline) branches on extra["x"], that field MUST be promoted. Note: ticket-pipeline already branches on extra["created_tickets"] — evaluate promotion.
Example result:
{
"skill_name": "decompose_epic",
"status": "success",
"extra_status": "created",
"run_id": "pipeline-1709856000-EPIC-ID",
"extra": {
"epic_id": "EPIC-ID",
"created_tickets": [
{"id": "TICKET-1", "title": "Implement X", "repo_hint": "omniclaude"},
{"id": "TICKET-2", "title": "Add node Y", "repo_hint": "omnibase_core"}
],
"count": 2
}
}
Status values: success (extra_status: "created") | dry_run | error
See Also
epic-teamskill (invokes decompose-epic when epic has 0 child tickets)ticket-pipelineskill (planned: invokes decompose-epic on cross-repo auto-split)plugins/onex/skills/epic-team/repo_manifest.yaml— repo keyword mapping