Skip to content

Integration: Add githubDiscussions source type for community Q&A automation and RFC processing #768

@kelos-bot

Description

@kelos-bot

🤖 Kelos Strategist Agent @gjkim42

Area: Integration Opportunities

Summary

GitHub Discussions is a first-class feature used by thousands of projects for Q&A, feature proposals, RFCs, and community engagement. Kelos currently supports githubIssues, githubPullRequests, cron, and jira as TaskSpawner sources, but has no way to discover work items from GitHub Discussions. This proposal adds a githubDiscussions source type that enables agents to automate community support, triage feature requests, and analyze RFCs — addressing one of the most time-consuming aspects of open-source maintenance.

Problem

1. Community Q&A is a massive time sink for maintainers

Large open-source projects receive hundreds of discussion questions monthly. Popular projects like Next.js (~18k discussions), React (~4k discussions), and VS Code (~8k discussions) rely on GitHub Discussions as their primary support channel. Maintainers spend significant time answering repetitive questions that could be answered by searching the codebase and documentation.

Unlike GitHub Issues (which represent tracked work), Discussions are conversational and often require knowledge lookup rather than code changes — making them ideal for AI agent assistance.

2. Feature request triage is inconsistent and delayed

Many projects use GitHub Discussions for feature proposals (the "Ideas" category). These proposals often sit unanswered for weeks because maintainers lack time to:

  • Assess technical feasibility
  • Identify affected code areas
  • Estimate implementation scope
  • Check for duplicate requests

An agent could perform this initial analysis immediately, giving maintainers structured information for prioritization.

3. No existing Kelos source covers this

  • githubIssues uses the REST Issues API — Discussions are a completely separate API (GraphQL-only)
  • Discussions have unique semantics: categories (Q&A, Ideas, General), accepted answers, threaded replies, upvote counts
  • The types: ["issues", "pulls"] filter on githubIssues cannot include discussions
  • Generic webhooks (Integration: Add generic webhook source type to TaskSpawner for universal event-driven task triggering #687), when implemented, could receive Discussion events but lack the polling/discovery model and structured metadata that a dedicated source provides

Proposed API

Add a githubDiscussions field to the When struct in api/v1alpha1/taskspawner_types.go:

type When struct {
    GitHubIssues        *GitHubIssues        `json:"githubIssues,omitempty"`
    GitHubPullRequests  *GitHubPullRequests  `json:"githubPullRequests,omitempty"`
    Cron                *Cron                `json:"cron,omitempty"`
    Jira                *Jira                `json:"jira,omitempty"`
    GitHubDiscussions   *GitHubDiscussions   `json:"githubDiscussions,omitempty"`  // NEW
}

// GitHubDiscussions discovers discussions from a GitHub repository.
// Uses the GitHub GraphQL API (Discussions are not available via REST).
// Authentication is provided via the workspace's secretRef.
type GitHubDiscussions struct {
    // Repo optionally overrides the repository to poll for discussions,
    // in "owner/repo" format. When empty, derived from workspace repo URL.
    // +optional
    Repo string `json:"repo,omitempty"`

    // Category filters discussions by category name (e.g., "Q&A", "Ideas",
    // "General"). When empty, all categories are included.
    // +optional
    Category string `json:"category,omitempty"`

    // Answered filters Q&A discussions by answer state.
    // "unanswered" returns only discussions without an accepted answer.
    // "answered" returns only discussions with an accepted answer.
    // "any" returns all discussions regardless of answer state.
    // Only meaningful for Q&A category discussions.
    // +kubebuilder:validation:Enum=unanswered;answered;any
    // +kubebuilder:default=any
    // +optional
    Answered string `json:"answered,omitempty"`

    // Labels filters discussions by labels.
    // +optional
    Labels []string `json:"labels,omitempty"`

    // ExcludeLabels filters out discussions that have any of these labels.
    // +optional
    ExcludeLabels []string `json:"excludeLabels,omitempty"`

    // CommentPolicy configures comment-based workflow control.
    // +optional
    CommentPolicy *GitHubCommentPolicy `json:"commentPolicy,omitempty"`

    // Reporting configures status reporting back to the discussion.
    // +optional
    Reporting *GitHubReporting `json:"reporting,omitempty"`

    // PollInterval overrides spec.pollInterval for this source.
    // +optional
    PollInterval string `json:"pollInterval,omitempty"`
}

WorkItem mapping

GitHub Discussion fields map to the existing WorkItem struct:

WorkItem field Discussion value
ID Discussion node ID (GraphQL global ID)
Number Discussion number
Title Discussion title
Body Discussion body
URL Discussion HTML URL
Labels Discussion labels
Comments Concatenated discussion comments (threaded)
Kind "Discussion" (new value)

PromptTemplate variables

All existing template variables work naturally:

promptTemplate: |
  Discussion #{{.Number}}: {{.Title}}
  Category: Q&A
  
  Question:
  {{.Body}}
  
  Existing replies:
  {{.Comments}}
  
  Search the codebase and documentation to answer this question.
  Post your answer as a reply on the discussion: {{.URL}}

Implementation

New source: internal/source/github_discussions.go

Unlike the existing GitHub sources (which use REST), this source uses the GitHub GraphQL API because Discussions are only available via GraphQL:

type GitHubDiscussionsSource struct {
    Owner           string
    Repo            string
    Category        string
    Answered        string // "unanswered", "answered", "any"
    Labels          []string
    ExcludeLabels   []string
    Token           string
    BaseURL         string // For GitHub Enterprise
    Client          *http.Client
    TriggerComment  string
    ExcludeComments []string
    // ... other comment policy fields
}

func (s *GitHubDiscussionsSource) Discover(ctx context.Context) ([]WorkItem, error) {
    // 1. Query discussions via GraphQL
    // 2. Filter by category, labels, answered state
    // 3. Apply comment policy (trigger/exclude)
    // 4. Map to WorkItem structs
}

GraphQL query structure

query($owner: String!, $repo: String!, $categoryId: ID, $after: String) {
  repository(owner: $owner, name: $repo) {
    discussions(
      first: 25
      after: $after
      categoryId: $categoryId
      orderBy: { field: UPDATED_AT, direction: DESC }
    ) {
      nodes {
        id
        number
        title
        body
        url
        isAnswered
        labels(first: 10) { nodes { name } }
        comments(first: 20) {
          nodes {
            body
            createdAt
            author { login }
            isAnswer
          }
        }
      }
      pageInfo { hasNextPage endCursor }
    }
  }
}

The category ID is resolved with a separate query on first use:

query($owner: String!, $repo: String!) {
  repository(owner: $owner, name: $repo) {
    discussionCategories(first: 25) {
      nodes { id name }
    }
  }
}

Changes to spawner

In cmd/kelos-spawner/main.go, extend buildSource():

if ts.Spec.When.GitHubDiscussions != nil {
    gd := ts.Spec.When.GitHubDiscussions
    token, err := readGitHubToken(tokenFile)
    if err != nil {
        return nil, err
    }
    commentPolicy, err := resolveGitHubCommentPolicy(gd.CommentPolicy, "", nil)
    if err != nil {
        return nil, err
    }
    return &source.GitHubDiscussionsSource{
        Owner:           owner,
        Repo:            repo,
        Category:        gd.Category,
        Answered:        gd.Answered,
        Labels:          gd.Labels,
        ExcludeLabels:   gd.ExcludeLabels,
        Token:           token,
        BaseURL:         apiBaseURL,
        Client:          httpClient,
        TriggerComment:  commentPolicy.TriggerComment,
        ExcludeComments: commentPolicy.ExcludeComments,
        // ... other comment policy fields
    }, nil
}

Changes to controller

In internal/controller/taskspawner_controller.go and taskspawner_deployment_builder.go, extend the source detection logic to recognize githubDiscussions as a polling-based source (creates a Deployment, same as githubIssues). The controller changes are minimal — the existing patterns for deriving --github-owner and --github-repo from the workspace apply directly.

Reporting

GitHub Discussions support comments via GraphQL mutation (addDiscussionComment). The existing GitHubReporting mechanism can be extended to post status comments on discussions, following the same pattern as issue/PR reporting in internal/reporting/github.go.

Example TaskSpawner configurations

1. Automated Q&A responder

apiVersion: kelos.dev/v1alpha1
kind: TaskSpawner
metadata:
  name: qa-responder
spec:
  when:
    githubDiscussions:
      category: "Q&A"
      answered: unanswered
      excludeLabels: ["no-bot"]
      reporting:
        enabled: true
      pollInterval: "10m"
  maxConcurrency: 2
  taskTemplate:
    type: claude-code
    credentials:
      type: oauth
      secretRef:
        name: claude-creds
    workspaceRef:
      name: my-project
    branch: "main"  # Read-only, no branch creation needed
    promptTemplate: |
      A user asked a question in GitHub Discussion #{{.Number}}: {{.Title}}

      Question:
      {{.Body}}

      Existing replies (if any):
      {{.Comments}}

      Search the codebase, documentation, and existing discussions to find
      the answer. Then post a helpful reply to the discussion at {{.URL}}
      using `gh api graphql`.

      If you cannot find a definitive answer, say so honestly and suggest
      where the user might find help.
    ttlSecondsAfterFinished: 3600

2. Feature request analyzer

apiVersion: kelos.dev/v1alpha1
kind: TaskSpawner
metadata:
  name: feature-analyzer
spec:
  when:
    githubDiscussions:
      category: "Ideas"
      labels: ["needs-analysis"]
      commentPolicy:
        triggerComment: "/analyze"
        allowedUsers: ["maintainer1", "maintainer2"]
      pollInterval: "15m"
  maxConcurrency: 1
  taskTemplate:
    type: claude-code
    credentials:
      type: oauth
      secretRef:
        name: claude-creds
    workspaceRef:
      name: my-project
    promptTemplate: |
      Analyze this feature request from Discussion #{{.Number}}: {{.Title}}

      Proposal:
      {{.Body}}

      Community feedback:
      {{.Comments}}

      Perform the following analysis and post a structured reply:
      1. **Feasibility**: Search the codebase to assess how this could be implemented
      2. **Affected areas**: List files and modules that would need changes
      3. **Scope estimate**: Small (1-2 files), Medium (3-10 files), or Large (10+ files)
      4. **Related work**: Check for similar existing functionality or past proposals
      5. **Recommendation**: Suggest next steps (create issue, needs RFC, already possible, etc.)
    ttlSecondsAfterFinished: 3600

3. RFC impact analysis

apiVersion: kelos.dev/v1alpha1
kind: TaskSpawner
metadata:
  name: rfc-analyzer
spec:
  when:
    githubDiscussions:
      category: "RFCs"
      labels: ["rfc"]
      commentPolicy:
        triggerComment: "/impact"
      pollInterval: "30m"
  maxConcurrency: 1
  taskTemplate:
    type: claude-code
    credentials:
      type: oauth
      secretRef:
        name: claude-creds
    workspaceRef:
      name: my-project
    promptTemplate: |
      Perform an impact analysis for RFC #{{.Number}}: {{.Title}}

      RFC content:
      {{.Body}}

      Discussion:
      {{.Comments}}

      Analyze the codebase and produce:
      1. List of files/packages that would be affected
      2. API surface changes (breaking vs non-breaking)
      3. Test coverage gaps for affected areas
      4. Migration considerations for existing users
      5. Dependencies that may be impacted

      Post the analysis as a reply to the discussion at {{.URL}}.
    ttlSecondsAfterFinished: 7200

Technical considerations

GraphQL vs REST

This is the first Kelos source to require GraphQL. Design choices:

  • No external GraphQL library: Use raw HTTP POST requests with JSON-encoded queries (same pattern as the Jira source, which also uses POST requests). This avoids adding a new dependency.
  • Shared HTTP client: Reuse the existing ETag-aware HTTP client. GitHub's GraphQL API doesn't support ETag caching, but the transport gracefully handles this (no conditional request, full response returned).
  • Rate limiting: GitHub GraphQL API has a separate rate limit (5,000 points/hour) from REST (5,000 requests/hour). A single discussions query costs ~1-2 points. At the default 10-minute poll interval, a spawner uses ~10-20 points/hour — well within limits.

GitHub Enterprise compatibility

GitHub Enterprise Server supports GraphQL and Discussions (since GHES 3.7). The baseURL flag is already passed to all sources. For GraphQL, the endpoint is {baseURL}/graphql instead of {baseURL}/api/graphql. The source should detect and handle both patterns.

Scope

File Change
api/v1alpha1/taskspawner_types.go Add GitHubDiscussions struct and field to When
api/v1alpha1/zz_generated.deepcopy.go make update regenerates
internal/source/github_discussions.go New source implementation (~200 lines)
internal/source/github_discussions_test.go Unit tests for discovery, filtering, comment policy
cmd/kelos-spawner/main.go Add githubDiscussions branch to buildSource()
internal/controller/taskspawner_controller.go Recognize githubDiscussions as polling-based source
internal/controller/taskspawner_deployment_builder.go Pass flags for discussions source
internal/reporting/github.go Add GraphQL comment mutation for discussion reporting
CRD manifests make update to regenerate
docs/reference.md Document new source type

No new dependencies needed. No changes to the Task CRD, Workspace CRD, or AgentConfig CRD.

Backward compatibility

  • Purely additive: new optional field on When
  • Existing TaskSpawners are unaffected
  • No migration needed
  • Uses the same workspace auth pattern as other GitHub sources

Why this matters for Kelos adoption

  1. Large addressable market: GitHub reports 400M+ discussion comments across the platform. Projects with active Discussions face the same automation gap that Kelos already solves for Issues and PRs.
  2. Low barrier to entry: Teams already using Kelos with githubIssues can add a Discussions spawner with the same workspace and credentials — zero new infrastructure.
  3. Unique positioning: No competing Kubernetes-native agent framework offers Discussion-driven automation. This differentiates Kelos from tools like Copilot Workspace or Devin, which focus exclusively on code changes.
  4. Natural upgrade path: Teams start with Q&A automation (low risk, high visibility), then expand to feature request analysis and RFC processing — deepening their investment in Kelos.

Related issues

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions