Skip to content

Blog

Understanding Agile Work Hierarchy: Stories, Epics, and Initiatives

In distributed teams, having a shared understanding of Agile terminology is essential to working effectively. This post explains the hierarchy of Agile work items and how they fit together to support clarity, alignment, and execution.

🧩 Work Item Hierarchy Overview

graph TD
    %% the preview is rendering the bottom
    %% subgraph first, so switching them here
    subgraph Initiative 2
        I2E1[Epic]
        I2E2[Epic]
        I2S1["Story: 'What?' & 'Why?'"]
        I2S2["Story: 'What?' & 'Why?'"]
        I2T1["Task: 'How?'"]
        I2T2["Task: 'How?'"]
        I2T3["Task: 'How?'"]
        I2T4["Task: 'How?'"]
        I2E1 --> I2S1
        I2E2 --> I2S2
        I2S1 --> I2T1
        I2S1 --> I2T2
        I2S2 --> I2T3
        I2S2 --> I2T4
    end

    subgraph Initiative 1
        I1E1[Epic]
        I1E2[Epic]
        I1S1["Story: 'What?' & 'Why?'"]
        I1S2["Story: 'What?' & 'Why?'"]
        I1T1["Task: 'How?'"]
        I1T2["Task: 'How?'"]
        I1T3["Task: 'How?'"]
        I1T4["Task: 'How?'"]
        I1E1 --> I1S1
        I1E2 --> I1S2
        I1S1 --> I1T1
        I1S1 --> I1T2
        I1S2 --> I1T3
        I1S2 --> I1T4
    end

    Initiative1[Initiative 1] --> I1E1
    Initiative1 --> I1E2
    Initiative2[Initiative 2] --> I2E1
    Initiative2 --> I2E2

📝 Definitions

Level Description
🧱 Task The "how?" – implementation-level steps proving stories are fulfilled.
📗 Story The "what?" and "why?" – user-centric requirements with criteria.
📘 Epic A collection of related stories forming a larger feature.
🗂 Initiative Strategic objective spanning multiple epics.

Quick Analogy:

Stories = Requirements. Tasks = Implementation.

🌍 Why It Matters for Distributed Teams

  • ✍️ Shared Vocabulary: Avoids confusion across locations.
  • 🎯 Goal Alignment: Connects daily work to strategic initiatives.
  • 🔍 Traceability: Tasks trace back to story requirements.

For a deeper, evolving reference guide, see the Agile Work Hierarchy Reference page.

Agile Story vs Task

Note

This is meant to be way to help understand and develop a process for tracking work in a distributed team.

Agile Story vs Task

In Agile frameworks like Scrumban, understanding the distinction between a task and a story is crucial for effective project management. A user story is typically functionality that will be visible to end users and captures requirements and acceptance criteria from the user's perspective. Developing a user story usually involves multiple roles such as a programmer, tester, user interface designer, or analyst, indicating that it contains multiple types of work. 03

On the other hand, a task is a unit of work that is generally worked on by one person and is restricted to a single type of work. Tasks are implementation activities designed to prove that the requirements and acceptance criteria of user stories have been met. They are often technical in nature, such as implementing a class, setting up a virtual machine, writing a script, or conducting UI testing. 02

In the context of Jira, a popular tool for Agile project management, a Story is a more specific version of a Task. Both are work requests, but a Story was created to help people tracking User Stories in Jira. Tasks in Jira are considered "units" of work and are often used for activities that are not testable, whereas stories are for functionalities that can be tested and potentially shipped. 04

Scrumban, a hybrid of Scrum and Kanban, utilizes both concepts but emphasizes visualizing work, limiting work in progress, and maximizing efficiency. In Scrumban, tasks are represented as cards on a Kanban board, moving through different stages of the process, while stories are part of the product backlog and are prioritized based on complexity and product demand. 07

The key to distinguishing between a story and a task lies in understanding that stories bring functionality and value that is recognizable to the user, while tasks are the steps taken by developers to realize that functionality. 02 This distinction helps in organizing work in a way that aligns with both the user's needs and the team's capacity to deliver. 08

Work tracking in a distributed team

Note

This is meant to be a visual overview of how to manage issues as part of an overall work tracking process.

As mentioned in the Using Scrumban in a distributed team post, using Sprints to plan and define the work that will be completed can be extremely helpful in a distributed team. There should be a formally established process to follow in order to help everyone understand expectations. In this scenario, we can find out how we can use GitHub issues to plan and track work which can be helpful given the recent changes the GitHub team is making to issues and projects.

Agile Project Management Terminology

What are stories, epics, and initiatives? (from atlassian.com)

  • Stories, also called “user stories,” are short requirements or requests written from the perspective of an end user.
  • Epics are large bodies of work that can be broken down into a number of smaller tasks (called stories).
  • Initiatives are collections of epics that drive toward a common goal.
flowchart TD
    %% the preview is rendering the bottom
    %% subgraph first, so switching them here
    subgraph Initiative 2
        I2E1[Epic C]
        I2E2[Epic D]
        I2S1[Story C1]
        I2S2[Story D1]
        I2E1 --> I2S1
        I2E2 --> I2S2
    end

    subgraph Initiative 1
        I1E1[Epic A]
        I1E2[Epic B]
        I1S1[Story A1]
        I1S2[Story B1]
        I1E1 --> I1S1
        I1E2 --> I1S2
    end

    Initiative1[Initiative 1] --> I1E1
    Initiative1 --> I1E2
    Initiative2[Initiative 2] --> I2E1
    Initiative2 --> I2E2

Including Tasks to use with GitHub Projects

We can build on the Agile project management terminology by adding tasks as a subset of either a story or an epic. The differences are explained more in the Agile Story vs Task post and the Understanding Agile Work Hierarchy post.

For a deeper, evolving reference guide, see the Agile Work Hierarchy Reference page.

Legends in mermaid diagrams

It might be helpful to add a legend to graphs using Mermaid.

Support for Legends in a Graph

Here are some examples to test workarounds for legends in a graph from mermaid-js/mermaid#2110

Legends in a Graph Example 1

This is from mermaid-js/mermaid#2110 comment 1057895108:

flowchart LR
    TF1<--->|hand off|TF2
    subgraph Translators
        direction LR
        TF2[Translation Files]<-->Pipeline
        subgraph Pipeline
            direction TB
            create-->review
            review-->approve
            approve-->maintain
            maintain-->review
        end
    end
    subgraph Developers
    TF1[Translation Files]-->|reference|Code
    Code-->|extract|TF1
    end
    subgraph Legend
      direction LR
      start1[ ] --->|fully automatable| stop1[ ]
      style start1 height:0px;
      style stop1 height:0px;
      start2[ ] --->|highly automatable| stop2[ ]
      style start2 height:0px;
      style stop2 height:0px; 
    end
    linkStyle 0 stroke:red;
    linkStyle 2 stroke:red;
    linkStyle 3 stroke:orange;
    linkStyle 4 stroke:orange;
    linkStyle 5 stroke:orange;
    linkStyle 6 stroke:red;
    linkStyle 7 stroke:red;
    linkStyle 8 stroke:red;
    linkStyle 9 stroke:orange;

Legends in a Graph Example 2

This is from mermaid-js/mermaid#2110 comment 2696764562:

The diagram was commented out due to an error...

Blog post template

This is a template file for blog posts in MkDocs. In order to keep it simple to create new posts, the template file should have the following:

Page configuration

When creating a new blog post, determine the following page configuration options:

Page metadata

When creating a new blog post, determine the following page metadata:

Putting it all together

Front-matter

The front-matter consists of YAML Style Meta-Data to define the page configuration settings:

---
# page configuration
title: Blog post template
description: >
  This is a template file for blog posts in MkDocs.
icon: octicons/repo-template-24
status: new
# page metadata
draft: false
date:
  created: 2025-02-18
  updated: 2025-02-18
authors:
  - rwaight
categories:
  - MkDocs
slug: blog-post-template
tags:
  - MkDocs
  - Template
---

Example section

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla et euismod nulla. Curabitur feugiat, tortor non consequat finibus, justo purus auctor massa, nec semper lorem quam in massa.

Personal time tracking

Notes about self-tracking time spent on different projects each week using different tools.

Review Slack messages sent during the week

Within Slack, you can search for messages you have sent using the following:

  • from:@SlackUsername
  • before:2025-02-08
  • after:2025-02-02

The actual query would be:

from:@SlackUsername before:2025-02-08 after:2025-02-02

Review GitHub commits during the week

GitHub Docs - Searching commits

Get a list of commits by author or committer:

# using the 'author'
https://github.com/search?q=author%3Agithubusername&type=commits&s=committer-date&o=desc&p=1
# using the 'committer'
https://github.com/search?q=committer%3Agithubusername&type=commits&s=committer-date&o=desc
# using the 'committer-name'
https://github.com/search?q=committer-name%3Agithubusername&type=commits&s=committer-date&o=desc

Try to use the date ranges to get a list of commits, by authored or committed date:

https://github.com/search?q=author%3Agithubusername&type=commits&s=committer-date&created%3A%3E2025-02-07&o=desc

Other syntax to try:

author-date%3A<2025-02-02&type=Commits

author-date
author-date:>2016-01-01

https://github.com/search?q=created%3A%3E2025-02-07&type=Repositories&ref=advsearch&l=&l=

Using your Google Calendar to keep track of time

If you use Google Calendar, you can also use Google Apps Script to print a list of calendar events for a specified date range.

  • Create a new project in Google Apps Script, you can name it Calendar info
  • In the project, create a new file named CalendarEntries
  • Add the following functions:
// Global variables
var START_DATE = new Date('2025-02-23');
var END_DATE = new Date('2025-03-01');  // End date is exclusive, be sure to pick the date AFTER you want to search through
var CALENDAR_ID = 'myemail@gmail.com';

function logAllEventStatuses() {
  var calendar = CalendarApp.getCalendarById(CALENDAR_ID);
  var events = calendar.getEvents(START_DATE, END_DATE);

  events.forEach(function(event) {
    Logger.log('Event: ' + event.getTitle() + ', My Status: ' + event.getMyStatus());
  });
}

function listCalendarEntries() {
  var calendar = CalendarApp.getCalendarById(CALENDAR_ID);
  var events = calendar.getEvents(START_DATE, END_DATE);

  Logger.log('Total events retrieved: ' + events.length);

  // Group events by date
  var eventsByDate = {};
  events.forEach(function(event) {
    var dateStr = Utilities.formatDate(event.getStartTime(), Session.getScriptTimeZone(), 'yyyy-MM-dd');
    if (!eventsByDate[dateStr]) {
      eventsByDate[dateStr] = [];
    }
    // Calculate duration in hours as a decimal
    var durationHours = (event.getEndTime() - event.getStartTime()) / (1000 * 60 * 60);
    eventsByDate[dateStr].push(`${event.getTitle()} (Duration: ${durationHours} hours)`);
  });

  // Create a text output grouped by date
  var output = '';
  Object.keys(eventsByDate).sort().forEach(function(date) {
    output += date + ':\n';
    eventsByDate[date].forEach(function(eventText) {
      output += ' - ' + eventText + '\n';
    });
    output += '\n';
  });

  Logger.log(output);
}

function logAcceptedEvents() {
  // this function does not work as intended yet, need to find out the proper 'myStatus' filter 
  //
  // For this function, you can decide if you want to use a different date range.
  // For now, we'll reuse the global dates.
  var calendar = CalendarApp.getCalendarById(CALENDAR_ID);
  var events = calendar.getEvents(START_DATE, END_DATE);

  Logger.log('Total events retrieved: ' + events.length);

  events.forEach(function(event) {
    var myStatus = event.getMyStatus();
    // Filter events that are either owned or accepted
    if (myStatus === 'OWNER' || myStatus === 'YES' || myStatus === 'accepted') {
      Logger.log('Accepted Event: ' + event.getTitle() +
                 ' | Start: ' + event.getStartTime() +
                 ' | End: ' + event.getEndTime() +
                 ' | Status: ' + myStatus);
    }
  });
}
  • Update the CALENDAR_ID with your email address
  • Update the START_DATE to the first day you want to get calendar entries for
  • Update the END_DATE to the date AFTER you want to search through
    • Example: if you want to search through 2025-03-01, then enter 2025-03-02
  • Save the changes to the CalendarEntries file
Running the listCalendarEntries function

Once you have stored your email address in the CALENDAR_ID and updated the START_DATE and END_DATE variables, now you can run the listCalendarEntries function:

  • Confirm the variables have been set correctly
  • Select the listCalendarEntries in the drop down menu
  • Select the Run option in the menu

google-apps-script-select-function

Using Scrumban in a distributed team

Note

While this document is not in draft mode it is definitely not complete...

Being flexible and efficient can make working in a distributed team a great experience; sometimes though, you have to be the agent of change to help your team get there. While trying to understand what Scrumban actually is, it seemed best to type it up myself to reinforce what I have learned so far.

Scrumban combines two leading Agile methodologies—Scrum and Kanban—into a single process for getting work done. But what is scrumban? And why should you consider its unique approach?

These are notes based on the following resources:

What is Scrumban?

The Scrumban methodology combines the best features of Scrum and Kanban into a hybrid project management framework. It uses Scrum's stable structure of sprints, standups, and retrospectives. Then it adds Kanban's visual workflow and work-in-progress limitations. The result is a truly flexible method for managing projects of any size.

Agile Ceremonies

An Agile ceremony is an event in the Agile process when your team meets to discuss what their next course of action is.

There are four major Agile ceremonies that happen during every sprint cycle. Before starting each ceremony, your team members should understand the purpose of each meeting and how it impacts the sprint.

The 4 Agile ceremonies

  1. The sprint planning meeting
  2. The daily stand-up meeting
  3. The sprint review meeting
  4. The sprint retrospective meeting

Intentionally timely

Timeboxed meetings

As mentioned in the stand-up meeting wikipedia page, the stand-up meetings are intentionally timeboxed:

The meetings are usually timeboxed to between 5 and 15 minutes, and take place with participants standing up to remind people to keep the meeting short and to-the-point.6

Keep the meeting short

The daily stand-up meeting (also known as a “daily scrum”, a “daily huddle”, “morning roll-call”, etc.) is simple to describe:

The whole team meets every day for a quick status update. We stand up to keep the meeting short.

That's it.

But this short definition does not really tell you the subtle details that distinguish an effective stand-up from a waste of time.

So how can you tell?

The answer to the question of an effective stand-up is addressed in the It's Not Just Standing Up: Patterns for Daily Standup Meetings article, which is linked from the stand-up meeting wikipedia page.

The Kanban board

GitHub projects offers one way to use a Kanban board, this is accomplished using custom views in your GitHub project, specifically the board layout, which is based on the status field.

Example status field settings

Here is an example of Status field settings for a GitHub project:

  • Triage: Items to be triaged
  • To do: Triaged items up for grabs. Please choose based on determined priorities.
  • In Progress: Items actively being worked on
  • Review Needed: PRs and issues that require review
  • Changes Requested: Reviewed PRs and issues with changes requested
  • Approved: Approved PRs that need to be merged, or approved items that need to be completed
  • Done: Items that have been completed

Here is what that would look like in the Status field settings page:

github-project-status-field-settings

Using GitHub project workflows to improve timeliness

Note

This section is incomplete and should be in a separate blog post. It is "to be continued"...

Dark mode in chromium browsers

If you use a chromium browser and love dark mode, then keep reading.

Setting dark mode for everything

If you love dark mode for everything... you can enable Auto Dark Mode for Web Contents by setting chrome://flags/#enable-force-dark. If that does not work, then go to chrome://flags/ and search for Dark Mode.

Referenced from How do I set Google Calendar to Dark Mode?

turn on the flag Force Dark Mode for Web Contents you can do this by going to chrome://flags/ and searching for it

Browser dark mode extensions

Reset a Cisco Catalyst Switch

Here is a summary of the commands needed to reset Cisco Catalyst Switches to factory defaults:

# enter configuration mode
enable
# remove the startup configuration file
write erase
# remove the VLAN configuration
delete flash:vlan.dat
# then reboot the switch
reload

If there are other files that need to be deleted, run the following before issuing the reload command:

show file information flash:?
delete flash:config.text.backup
delete flash:config.text.old
delete flash:*.old

Enable debug for verbose GitHub Actions

!!!? info

This post is incomplete and will be updated in the future.

If you are using composite actions that support their own verbose mode, you may find you only want to enable verbose mode when the GitHub runner is in debug mode. The variable we need to know is runner.debug, which is also stored as RUNNER_DEBUG.

Understanding when variables are available

In a GitHub workflow there are three different situations where different environmental variables are available, GitHub calls this "context". There is workflow context, job context, and step context. The runner.* and RUNNER_* variables are available in the STEP environmental context, but not in the workflow or job environmental context.

Example workflow using debug mode

What this means is that in order to find out what the runner.debug variable is set to, you must check for the variable in a step. Here is an example workflow that will run only when the GitHub runner is in debug mode:

name: Print information only in runner debug

on:
  push:
    branches:
      - 'main'
    # ignore changes to .md files and the entire .github directory
    paths-ignore:
      - '**.md'
      - '.github/**'

jobs:

  runner-debug:
    runs-on: ubuntu-latest
    name: Print info in runner debug mode
    steps:

      - name: GitHub Runner Debug Mode
        if: ${{ runner.debug == '1' }}
        # to do:  run this if either the verbose input is true or the runner.debug is true
        id: runner-debug-mode
        ## The 'runner.*' and 'RUNNER_*' variables are not available in the WORKFLOW env context or the top-level JOB context, but are available in the STEP env context
        shell: bash
        env:
            EVAL_GH_VAR_RUNNER_DEBUG_EQ0: ${{ runner.debug == '0' }}
            EVAL_GH_VAR_RUNNER_DEBUG_EQ1: ${{ runner.debug == '1' }}
            FOOBAR: ${{ runner.debug == '1' && 'foo' || 'bar' }}
            # https://github.com/actions/runner/issues/2204#issuecomment-1287947031
            # https://github.com/orgs/community/discussions/27627#discussioncomment-3302259
            GH_RUNNER_LOG: "${{ runner.debug == '1' && 'INFO' || 'ERROR' }}"
            GH_VAR_RUNNER_DEBUG1: ${{ runner.debug }}
            GH_VAR_RUNNER_DEBUG2: ${{ env.RUNNER_DEBUG }}
        run: |
            echo "::group::starting the 'print-runner-context' step... "
            echo ""
            echo "NOTE: The 'runner.*' and 'RUNNER_*' variables are not available in the WORKFLOW env context or the top-level JOB context, but are available in the STEP env context "
            echo ""
            echo "eval if the 'runner.debug' is set to either '0' or '1' "
            echo "     runner.debug equal 0:  ${EVAL_GH_VAR_RUNNER_DEBUG_EQ0} "
            echo "     runner.debug equal 1:  ${EVAL_GH_VAR_RUNNER_DEBUG_EQ1} "
            echo ""
            echo "set FOOBAR to 'foo' if 'runner.debug' is '1'; otherwise set FOOBAR to 'bar' "
            echo "    FOOBAR:  ${FOOBAR} "
            echo ""
            echo "set GH_RUNNER_LOG to 'INFO' if 'runner.debug' is '1'; otherwise set GH_RUNNER_LOG to 'ERROR' "
            echo "    GH_RUNNER_LOG:  ${GH_RUNNER_LOG} "
            echo ""
            echo "the values of 'runner.debug' and 'env.RUNNER_DEBUG': "
            echo "    GH_VAR_RUNNER_DEBUG1:  ${GH_VAR_RUNNER_DEBUG1} "
            echo "    GH_VAR_RUNNER_DEBUG2:  ${GH_VAR_RUNNER_DEBUG2} "
            echo ""
            echo "finishing the 'print-runner-context' step... "
            ##
            echo "::endgroup::"
        ## The 'runner.*' and 'RUNNER_*' variables are not available in the WORKFLOW env context or the top-level JOB context, but are available in the STEP env context