Skip to content

Instantly share code, notes, and snippets.

@mikehostetler
Created February 3, 2026 13:10
Show Gist options
  • Select an option

  • Save mikehostetler/c707ddf561b46f3a3dc36e36610781c5 to your computer and use it in GitHub Desktop.

Select an option

Save mikehostetler/c707ddf561b46f3a3dc36e36610781c5 to your computer and use it in GitHub Desktop.
GitHub Issue Bot Workflow Analysis - Comparing DAG, FSM, Behavior Tree, and HTN approaches for Jido ecosystem

GitHub Issue Bot Workflow Analysis

Problem Statement

Goal: Respond to a GitHub Issue submission by researching the problem and posting a Pull Request with a solution.


Standard Operating Procedure (SOP)

For Junior Engineers: Handling GitHub Issues

Phase 1: Intake & Triage

  1. Receive Issue Notification — Get alerted when a new issue is assigned or labeled
  2. Read & Parse Issue — Understand the problem statement, reproduction steps, expected behavior
  3. Classify Issue Type — Bug fix, feature request, documentation, or question
  4. Check Validity — Is this a duplicate? Is there enough information? Does it belong to this repo?
  5. Acknowledge Receipt — Post a comment confirming you're investigating

Phase 2: Research & Analysis

  1. Search Codebase — Find relevant files, modules, and tests related to the issue
  2. Reproduce Problem — If bug: create a failing test or reproduce locally
  3. Identify Root Cause — Trace through code to understand what's broken/missing
  4. Check for Existing Work — Are there related PRs, branches, or discussions?
  5. Document Findings — Note affected files, proposed approach, risks

Phase 3: Implementation

  1. Create Feature Branchgit checkout -b fix/issue-123-description
  2. Write Tests First — Create failing tests that will pass when fixed
  3. Implement Solution — Make code changes to address the issue
  4. Run Quality Checks — Tests, linting, type checking, formatting
  5. Self-Review — Review your own diff before submitting

Phase 4: Submission & Follow-up

  1. Create Pull Request — Link to issue, describe changes, add reviewers
  2. Update Issue — Comment with PR link and implementation notes
  3. Address Review Feedback — Iterate based on reviewer comments
  4. Merge & Close — Once approved, merge PR and close issue

Decision Points

  • 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

Workflow Model Comparison

1. DAG-Based Workflow (Jido.Plan)

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

2. State Machine (FSM Strategy)

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)

3. Behavior Tree

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

4. Hierarchical Task Network (HTN)

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

Comparison Matrix

Aspect DAG (Plan) FSM Behavior Tree HTN
Parallel execution ✅ Native ⚠️ Requires substates ⚠️ Explicit nodes ⚠️ Sequential
Conditional branching ⚠️ Plan rebuild ✅ Transitions ✅ Selectors ✅ Methods
Loops/retries ❌ Not native ✅ State cycles ✅ Decorators ⚠️ Replanning
State persistence ❌ Stateless ✅ Explicit ⚠️ Blackboard ⚠️ World state
Debugging/visibility ✅ Clear graph ✅ Current state ⚠️ Tree depth ⚠️ Plan trace
Dynamic adaptation ❌ Static ⚠️ Limited ⚠️ Limited ✅ Replanning
Composability ⚠️ Merge plans ❌ Monolithic ✅ Subtrees ✅ Domain libs
Implementation effort Low Medium Medium High

Recommendation for Jido Ecosystem

Hybrid Approach: FSM + DAG

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

Why This Works

  1. FSM gives visibility — Easy to answer "What state is this issue in?"
  2. DAG gives parallelism — Research tasks can run concurrently
  3. Both are simple — Lower cognitive overhead than BT or HTN
  4. Recovery is natural — Resume from current FSM state
  5. Fits Jido idioms — Both are already well-supported in the ecosystem

When to Consider Alternatives

  • 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

Next Steps

  1. Define the Action modules for each primitive step
  2. Implement FSM phase handlers
  3. Build DAG plans for each phase
  4. Add Thread integration for execution history
  5. Create GitHub webhook integration layer
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment