Goal: Respond to a GitHub Issue submission by researching the problem and posting a Pull Request with a solution.
- Receive Issue Notification — Get alerted when a new issue is assigned or labeled
- Read & Parse Issue — Understand the problem statement, reproduction steps, expected behavior
- Classify Issue Type — Bug fix, feature request, documentation, or question
- Check Validity — Is this a duplicate? Is there enough information? Does it belong to this repo?
- Acknowledge Receipt — Post a comment confirming you're investigating
- Search Codebase — Find relevant files, modules, and tests related to the issue
- Reproduce Problem — If bug: create a failing test or reproduce locally
- Identify Root Cause — Trace through code to understand what's broken/missing
- Check for Existing Work — Are there related PRs, branches, or discussions?
- Document Findings — Note affected files, proposed approach, risks
- Create Feature Branch —
git checkout -b fix/issue-123-description - Write Tests First — Create failing tests that will pass when fixed
- Implement Solution — Make code changes to address the issue
- Run Quality Checks — Tests, linting, type checking, formatting
- Self-Review — Review your own diff before submitting
- Create Pull Request — Link to issue, describe changes, add reviewers
- Update Issue — Comment with PR link and implementation notes
- Address Review Feedback — Iterate based on reviewer comments
- Merge & Close — Once approved, merge PR and close issue
- Step 4: If invalid/duplicate → close with explanation
- Step 7: If can't reproduce → request more info
- Step 10: If too complex → escalate to senior engineer
- Step 14: If tests fail → loop back to step 13
Concept: Steps as nodes, dependencies as edges. Parallel execution where possible.
┌─────────────────┐
│ receive_issue │
└────────┬────────┘
│
┌────────▼────────┐
│ parse_issue │
└────────┬────────┘
│
┌──────────────┼──────────────┐
│ │ │
┌────────▼────┐ ┌──────▼──────┐ ┌────▼────────┐
│classify_type│ │check_validity│ │check_dupes │
└────────┬────┘ └──────┬──────┘ └────┬────────┘
│ │ │
└──────────────┼──────────────┘
│
┌────────▼────────┐
│ research │ ◄── (search_codebase, reproduce, find_root_cause in parallel)
└────────┬────────┘
│
┌────────▼────────┐
│ create_branch │
└────────┬────────┘
│
┌────────▼────────┐
│ write_tests │
└────────┬────────┘
│
┌────────▼────────┐
│ implement │
└────────┬────────┘
│
┌────────▼────────┐
│ quality_check │
└────────┬────────┘
│
┌────────▼────────┐
│ create_pr │
└────────┬────────┘
Pseudo-code:
Plan.new()
|> Plan.add(:receive, ReceiveIssue)
|> Plan.add(:parse, ParseIssue, depends_on: :receive)
|> Plan.add(:classify, ClassifyType, depends_on: :parse)
|> Plan.add(:check_validity, CheckValidity, depends_on: :parse)
|> Plan.add(:check_dupes, CheckDuplicates, depends_on: :parse)
|> Plan.add(:triage, TriageDecision, depends_on: [:classify, :check_validity, :check_dupes])
|> Plan.add(:research, ResearchCodebase, depends_on: :triage)
|> Plan.add(:branch, CreateBranch, depends_on: :research)
|> Plan.add(:tests, WriteTests, depends_on: :branch)
|> Plan.add(:implement, ImplementFix, depends_on: :tests)
|> Plan.add(:quality, RunQualityChecks, depends_on: :implement)
|> Plan.add(:pr, CreatePullRequest, depends_on: :quality)Strengths:
- Clear visualization of parallel opportunities
- Simple dependency modeling
- Good for static, predictable workflows
Weaknesses:
- No native support for loops/retries
- Conditional branching requires plan reconstruction
- No persistent state between executions
Concept: Issue resolution as states, actions trigger transitions.
┌─────────────────────────────────────┐
│ │
▼ │
┌──────┐ receive ┌──────────┐ valid ┌────────────┐ │
│ IDLE │──────────►│ TRIAGING │─────────►│ RESEARCHING│ │
└──────┘ └──────────┘ └─────┬──────┘ │
▲ │ │ │
│ invalid │ analyzed │
│ │ ▼ │
│ ▼ ┌──────────────┐ │
│ ┌──────────┐ │ IMPLEMENTING │ │
│ │ REJECTED │ └──────┬───────┘ │
│ └──────────┘ │ │
│ │ tests_pass │
│ ▼ │
│ ┌─────────────────┐ │
│ │ REVIEWING │◄─────┤
│ └────────┬────────┘ │
│ │ │
│ approved │ feedback │
│ │ │ │ │
│ ▼ └────────┘ │
│ ┌──────────┐ │
└────────────────────────│ COMPLETED│ │
└──────────┘ │
│ │
tests_fail───────────────────────
Pseudo-code:
use Jido.Agent,
strategy: {Jido.Agent.Strategy.FSM,
initial_state: "idle",
transitions: %{
"idle" => ["triaging"],
"triaging" => ["researching", "rejected"],
"researching" => ["implementing"],
"implementing" => ["reviewing", "researching"], # can loop back
"reviewing" => ["completed", "implementing"], # can loop back
"completed" => ["idle"],
"rejected" => ["idle"]
}
}Strengths:
- Explicit state visibility (easy to query "where are we?")
- Natural support for loops (review → implement → review)
- Recovery-friendly (can resume from any state)
- Audit trail built-in
Weaknesses:
- Parallelism requires substates or nested FSMs
- Complex conditions can lead to state explosion
- Actions are implicit (transitions don't describe what happens)
Concept: Hierarchical decision tree with tick-based execution.
[Sequence: ProcessIssue]
│
┌────────────┬───────────────┼───────────────┬────────────┐
│ │ │ │ │
[Sequence] [Selector] [Sequence] [Sequence] [Sequence]
Intake Triage Research Implement Submit
│ │ │ │ │
┌────┴────┐ ┌───┴────┐ ┌────┴────┐ ┌────┴────┐ ┌────┴────┐
│ │ │ │ │ │ │ │ │ │
Receive Parse │ [Sel] Search Repro Branch Tests PR Update
│ Invalid Code Write Impl
┌───┴───┐
CheckDupe CheckValid
[Selector] = Try first child, if fails try next
[Sequence] = Run all children in order, fail if any fails
Pseudo-code:
Tree.new(
Sequence.new([
# Phase 1: Intake
Sequence.new([
Action.new(ReceiveIssue),
Action.new(ParseIssue)
]),
# Phase 2: Triage (selector - try valid path, fallback to reject)
Selector.new([
Sequence.new([
Action.new(CheckValidity),
Action.new(CheckDuplicates),
Action.new(AcknowledgeIssue)
]),
Action.new(RejectIssue) # fallback
]),
# Phase 3: Research
Sequence.new([
Action.new(SearchCodebase),
Action.new(ReproduceProblem),
Action.new(IdentifyRootCause)
]),
# Phase 4: Implement (with retry decorator)
Repeat.new(
Sequence.new([
Action.new(CreateBranch),
Action.new(WriteTests),
Action.new(ImplementFix),
Action.new(RunQualityChecks)
]),
max_retries: 3
),
# Phase 5: Submit
Sequence.new([
Action.new(CreatePR),
Action.new(UpdateIssue)
])
])
)Strengths:
- Natural for conditional logic (Selector = fallback, Sequence = all-or-nothing)
- Built-in retry/repeat patterns
- Composable and reusable subtrees
- Tick-based = easy to pause/resume
Weaknesses:
- Parallelism requires explicit Parallel nodes
- State sharing via blackboard can get messy
- Not naturally stateful (tree position not persisted by default)
- Debugging deep trees is harder
Concept: Goals decompose into tasks; planner finds a path.
┌───────────────────────────────┐
│ Goal: resolve_github_issue │
└───────────────┬───────────────┘
│ decomposes to
┌───────────────┼───────────────┐
│ │ │
┌──────▼──────┐ ┌──────▼──────┐ ┌──────▼──────┐
│ triage │ │ research │ │ deliver │
└──────┬──────┘ └──────┬──────┘ └──────┬──────┘
│ │ │
┌─────────┼─────────┐ │ ┌─────┼─────┐
│ │ │ │ │ │ │
receive validate classify │ branch impl pr
│
┌──────┼──────┐
│ │ │
search repro analyze
Compound Tasks: Goals that decompose into subtasks
Primitive Tasks: Actual actions (mapped to Jido.Action modules)
Pseudo-code:
Domain.new("IssueBotDomain")
# Compound: Top-level goal
|> Domain.compound("resolve_issue",
methods: [
%{subtasks: ["triage", "research", "deliver"],
conditions: [&issue_assigned?/1]}
])
# Compound: Triage phase
|> Domain.compound("triage",
methods: [
%{subtasks: ["receive_issue", "validate_issue", "classify_issue"],
conditions: [&issue_new?/1]}
])
# Compound: Research with fallback methods
|> Domain.compound("research",
methods: [
%{subtasks: ["search_codebase", "reproduce_bug", "identify_root_cause"],
conditions: [&is_bug?/1]},
%{subtasks: ["search_codebase", "analyze_feature_request"],
conditions: [&is_feature?/1]}
])
# Compound: Delivery
|> Domain.compound("deliver",
methods: [
%{subtasks: ["create_branch", "implement_solution", "run_checks", "create_pr"],
conditions: [&research_complete?/1]}
])
# Primitives - actual actions
|> Domain.primitive("receive_issue", {Actions.ReceiveIssue, []})
|> Domain.primitive("validate_issue", {Actions.ValidateIssue, []},
preconditions: [&issue_received?/1])
|> Domain.primitive("search_codebase", {Actions.SearchCode, []})
|> Domain.primitive("create_pr", {Actions.CreatePR, []},
preconditions: [&tests_passing?/1])Strengths:
- Most expressive for conditional decomposition — same goal, different strategies based on state
- Planner handles sequencing automatically
- Natural for replanning on failure
- Preconditions/effects model real-world constraints
Weaknesses:
- Higher cognitive overhead to design
- Planner can be computationally expensive
- Harder to visualize final execution path
- Less intuitive for simple linear workflows
| Aspect | DAG (Plan) | FSM | Behavior Tree | HTN |
|---|---|---|---|---|
| Parallel execution | ✅ Native | |||
| Conditional branching | ✅ Transitions | ✅ Selectors | ✅ Methods | |
| Loops/retries | ❌ Not native | ✅ State cycles | ✅ Decorators | |
| State persistence | ❌ Stateless | ✅ Explicit | ||
| Debugging/visibility | ✅ Clear graph | ✅ Current state | ||
| Dynamic adaptation | ❌ Static | ✅ Replanning | ||
| Composability | ❌ Monolithic | ✅ Subtrees | ✅ Domain libs | |
| Implementation effort | Low | Medium | Medium | High |
Primary: Use FSM Strategy for high-level phase management (triage → research → implement → review → complete).
Secondary: Within each FSM state, use Plan DAGs for parallel task execution.
defmodule IssueBotAgent do
use Jido.Agent,
strategy: {Jido.Agent.Strategy.FSM,
initial_state: "idle",
transitions: %{
"idle" => ["triaging"],
"triaging" => ["researching", "closed"],
"researching" => ["implementing"],
"implementing" => ["reviewing"],
"reviewing" => ["completed", "implementing"],
"completed" => ["idle"]
}
}
# Each phase transition runs a Plan DAG
def on_enter("researching", agent, ctx) do
plan = Plan.new()
|> Plan.add(:search, SearchCodebase)
|> Plan.add(:reproduce, ReproduceBug)
|> Plan.add(:find_files, FindRelevantFiles)
|> Plan.add(:analyze, AnalyzeRootCause,
depends_on: [:search, :reproduce, :find_files])
MyAgent.cmd(agent, {ExecutePlan, %{plan: plan}})
end
end- FSM gives visibility — Easy to answer "What state is this issue in?"
- DAG gives parallelism — Research tasks can run concurrently
- Both are simple — Lower cognitive overhead than BT or HTN
- Recovery is natural — Resume from current FSM state
- Fits Jido idioms — Both are already well-supported in the ecosystem
- Behavior Tree: If you need complex fallback logic or want reusable subtrees across multiple bots
- HTN: If issue types vary wildly and you need the planner to choose strategies dynamically
- Define the Action modules for each primitive step
- Implement FSM phase handlers
- Build DAG plans for each phase
- Add Thread integration for execution history
- Create GitHub webhook integration layer