githubprworkflow

package
v1.0.0-alpha.27 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Mar 11, 2026 License: MIT Imports: 8 Imported by: 0

README

GitHub Issue-to-PR Workflow

An adversarial automation workflow that watches a GitHub issue queue, qualifies issues, develops fixes, and reviews PRs before human involvement.

Architecture

GitHub Webhook ──> github-webhook (input)
                        |
                        v
                   NATS JetStream (GITHUB stream)
                        |
         +--------------+--------------+
         v              v              v
   Qualifier       Developer       Reviewer
   (agentic-loop)  (agentic-loop)  (agentic-loop)
         |              |              |
         +------+-------+------+------+
                v              v
          Reactive Workflow    GitHub Tools
          (coordination)       (executors)
                |
                v
          Knowledge Graph

Three Adversarial Agents

Qualifier (qualifier role) — Skeptical triage. Challenges every issue for reproducibility, clarity, and duplicates. Only qualified issues proceed to development.

Developer (developer role) — Disciplined implementer. Explores the codebase, plans minimal changes, writes tests, pushes a PR. Must satisfy the Reviewer.

Reviewer (reviewer role) — Critical code reviewer. Checks correctness, edge cases, test adequacy, and pattern compliance. Can reject PRs back to Developer.

Phase Flow

issue_received -> qualifying -> qualified -> developing -> dev_complete -> reviewing -> approved -> complete
                      |                          ^                            |
                      +-> rejected/needs_info    +---- changes_requested -----+
                                                 (max 3 review cycles, then escalate to human)

Prerequisites

  • NATS server with JetStream enabled
  • GITHUB_TOKEN environment variable (personal access token with repo scope)
  • GITHUB_WEBHOOK_SECRET environment variable (webhook HMAC secret)
  • An LLM endpoint (OpenAI-compatible)

Setup

  1. Configure a GitHub webhook pointing to http://your-host:8090/github/webhook
  2. Set the webhook secret and content type to application/json
  3. Subscribe to events: Issues, Pull requests, Pull request reviews, Issue comments
  4. Set environment variables:
# Required
export GITHUB_TOKEN="ghp_..."
export GITHUB_WEBHOOK_SECRET="your-webhook-secret"
export OPENAI_API_KEY="sk-..."

# Optional — override LLM provider (defaults to OpenAI/gpt-4o)
export LLM_PROVIDER="anthropic"                          # openai, anthropic, ollama, openrouter
export LLM_API_URL="https://api.anthropic.com/v1"        # API endpoint
export LLM_MODEL="claude-sonnet-4-20250514"                    # model identifier
export LLM_TOOL_FORMAT="anthropic"                       # anthropic or openai
export LLM_API_KEY_ENV="ANTHROPIC_API_KEY"               # which env var holds the API key
export ANTHROPIC_API_KEY="sk-ant-..."                    # the actual key

All LLM_* variables have sensible defaults (OpenAI/gpt-4o), so the minimum setup is just the three required vars.

  1. Run with the flow config:
go run ./cmd/semstreams -config configs/github-pr-workflow.json

LLM Configuration

The config uses ${VAR:-default} env var expansion so you can swap providers without editing the JSON:

Env Var Default Description
LLM_PROVIDER openai Provider name: openai, anthropic, ollama, openrouter
LLM_API_URL https://api.openai.com/v1 API endpoint URL
LLM_MODEL gpt-4o Model identifier
LLM_TOOL_FORMAT openai Tool calling format: openai or anthropic
LLM_API_KEY_ENV OPENAI_API_KEY Name of the env var that holds the actual API key

OpenAI (zero config):

export OPENAI_API_KEY="sk-..."

Anthropic:

export LLM_PROVIDER="anthropic"
export LLM_API_URL="https://api.anthropic.com/v1"
export LLM_MODEL="claude-sonnet-4-20250514"
export LLM_TOOL_FORMAT="anthropic"
export LLM_API_KEY_ENV="ANTHROPIC_API_KEY"
export ANTHROPIC_API_KEY="sk-ant-..."

Local Ollama (no API key needed):

export LLM_PROVIDER="ollama"
export LLM_API_URL="http://localhost:11434/v1"
export LLM_MODEL="qwen2.5:32b"

NATS Topology

Stream Subjects Purpose
GITHUB github.event.>, github.action.>, github.workflow.> Webhook events and workflow lifecycle
AGENT agent.>, tool.> Agent task dispatch, model calls, tool execution
KV Bucket Purpose
GITHUB_ISSUE_PR_STATE Workflow execution state
AGENT_LOOPS Agent loop state
AGENT_TRAJECTORIES Agent execution trajectories
ENTITY_STATES Knowledge graph entities

Cost Safety Controls

Built-in guardrails prevent runaway token spend:

Control Default What it does
Token budget per execution 500k tokens Caps total tokens (in+out) across all agents in one workflow run. Escalates to human when exceeded.
Issue cooldown 60s Minimum time between processing new issues. Prevents burst processing.
Max review cycles 3 Caps developer/reviewer back-and-forth. Escalates to human after 3 rejections.
Workflow timeout 30 min Hard wall-clock limit per execution.

When the token budget is exceeded mid-workflow, the execution escalates to a human rather than continuing to burn tokens. The completion payload includes total_tokens_in and total_tokens_out so you can monitor actual spend.

Constants are in workflow.go — adjust DefaultTokenBudget, DefaultIssueCooldown, etc. to taste.

Testing with a Toy Repo

To try this safely:

  1. Create a throwaway GitHub repo (e.g. yourname/workflow-test)
  2. Set repo_allowlist in the config to ["yourname/workflow-test"] to restrict which repos trigger workflows
  3. Configure the webhook on that repo only
  4. Open a simple issue like "Add a hello world endpoint" and watch the agents work
  5. Monitor token usage in the workflow completion events

Adversarial Tension

The quality funnel works through opposing pressures:

  • Qualifier vs Issues: Incentivized to reject. Vague, duplicate, or invalid issues are filtered out.
  • Reviewer vs Developer: Incentivized to find problems. Forces the Developer to produce correct, well-tested code.
  • Escalation: After 3 review cycles without convergence, or when the token budget is exceeded, the workflow escalates to a human with a full history summary.

Graph Entities

Issues, PRs, and reviews become graph entities, accumulating knowledge over time:

Entity ID Pattern Example
Issue {org}.github.repo.{repo}.issue.{n} c360.github.repo.semstreams.issue.42
PR {org}.github.repo.{repo}.pr.{n} c360.github.repo.semstreams.pr.87
Review {org}.github.repo.{repo}.review.{id} c360.github.repo.semstreams.review.rv-001

Relationship triples connect them: pr.fixes -> issue, review.targets -> pr, pr.modifies -> file.

Documentation

Overview

Package githubprworkflow provides Graphable entity types for the GitHub issue-to-PR automation workflow.

These entities model GitHub artifacts (issues, pull requests, reviews) as first-class graph nodes, enabling the knowledge graph to reason about repository activity and code change history.

Package githubprworkflow demonstrates a reactive workflow that automates the path from a GitHub issue to a merged pull request. Three adversarial agents — qualifier, developer, and reviewer — iterate against each other until the PR is approved or the review cycle limit is reached.

Index

Constants

View Source
const (
	// StateBucket is the NATS KV bucket that stores workflow execution state.
	StateBucket = "GITHUB_ISSUE_PR_STATE"

	// MaxReviewCycles is the maximum number of reviewer rejection/retry loops
	// before the workflow escalates to human intervention.
	MaxReviewCycles = 3

	// WorkflowTimeout is the maximum wall-clock duration for any single
	// execution before the engine marks it as timed out.
	WorkflowTimeout = 30 * time.Minute

	// DefaultTokenBudget is the maximum tokens (in+out) per workflow execution.
	// ~$5 at GPT-4o pricing — enough for qualify + develop + review.
	DefaultTokenBudget = 500_000

	// DefaultMaxConcurrentWorkflows limits parallel active workflows.
	DefaultMaxConcurrentWorkflows = 3

	// DefaultHourlyTokenCeiling is the global hourly token limit across all workflows.
	// ~$20/hour hard ceiling.
	DefaultHourlyTokenCeiling = 2_000_000

	// DefaultIssueCooldown is the minimum time between processing new issues.
	DefaultIssueCooldown = 60 * time.Second
)
View Source
const (
	PhaseQualified        = "qualified"
	PhaseRejected         = "rejected"
	PhaseNotABug          = "not_a_bug"
	PhaseWontFix          = "wont_fix"
	PhaseNeedsInfo        = "needs_info"
	PhaseAwaitingInfo     = "awaiting_info"
	PhaseDevComplete      = "dev_complete"
	PhaseDeveloping       = "developing"
	PhaseApproved         = "approved"
	PhaseChangesRequested = "changes_requested"
	PhaseEscalated        = "escalated"
)

Phase constants represent the distinct states an issue-to-PR execution can occupy. They are written into IssueToPRState.Phase and drive all KV-triggered rule conditions.

Variables

This section is empty.

Functions

func NewIssueToPRWorkflow

func NewIssueToPRWorkflow() *reactive.Definition

NewIssueToPRWorkflow builds the reactive workflow definition for the adversarial issue qualification, development, and review pipeline.

func PhaseIsActive

func PhaseIsActive(phase string) bool

PhaseIsActive returns true if the phase indicates an active workflow.

Types

type GitHubIssueEntity

type GitHubIssueEntity struct {
	// Org is the GitHub organization or user that owns the repository.
	Org string `json:"org"`
	// Repo is the repository name within the organization.
	Repo string `json:"repo"`
	// Number is the issue number within the repository.
	Number int `json:"number"`
	// Title is the issue title.
	Title string `json:"title"`
	// Body is the issue body text.
	Body string `json:"body"`
	// Labels contains any labels applied to the issue.
	Labels []string `json:"labels,omitempty"`
	// State is the current issue state (open, closed).
	State string `json:"state"`
	// Author is the GitHub username of the issue author.
	Author string `json:"author"`
	// Severity is an optional triage severity classification (e.g., critical, high, medium, low).
	Severity string `json:"severity,omitempty"`
	// Complexity is an optional effort estimation (e.g., small, medium, large).
	Complexity string `json:"complexity,omitempty"`
}

GitHubIssueEntity represents a GitHub issue as a graph node. It implements the Graphable interface to expose issue facts as semantic triples.

func (*GitHubIssueEntity) EntityID

func (e *GitHubIssueEntity) EntityID() string

EntityID returns the 6-part federated identifier for this issue. Format: {org}.github.repo.{repo}.issue.{number}

func (*GitHubIssueEntity) MarshalJSON

func (e *GitHubIssueEntity) MarshalJSON() ([]byte, error)

MarshalJSON implements json.Marshaler using the alias pattern to avoid recursion.

func (*GitHubIssueEntity) Schema

func (e *GitHubIssueEntity) Schema() message.Type

Schema returns the message type for GitHubIssueEntity payloads.

func (*GitHubIssueEntity) Triples

func (e *GitHubIssueEntity) Triples() []message.Triple

Triples returns semantic facts about this GitHub issue. Core facts (title, state, author) are always included; optional fields (severity, complexity, labels) are only included when set.

func (*GitHubIssueEntity) UnmarshalJSON

func (e *GitHubIssueEntity) UnmarshalJSON(data []byte) error

UnmarshalJSON implements json.Unmarshaler using the alias pattern to avoid recursion.

func (*GitHubIssueEntity) Validate

func (e *GitHubIssueEntity) Validate() error

Validate checks that the issue entity has all required fields.

type GitHubIssueWebhookEvent

type GitHubIssueWebhookEvent struct {
	Action string      `json:"action"`
	Issue  IssueDetail `json:"issue"`
	Repo   RepoDetail  `json:"repository"`
}

GitHubIssueWebhookEvent is a minimal representation of the GitHub webhook payload for issue events. Only the fields required by the workflow rules are modelled here; the full payload is not needed for routing.

type GitHubPREntity

type GitHubPREntity struct {
	// Org is the GitHub organization or user that owns the repository.
	Org string `json:"org"`
	// Repo is the repository name within the organization.
	Repo string `json:"repo"`
	// Number is the pull request number within the repository.
	Number int `json:"number"`
	// Title is the pull request title.
	Title string `json:"title"`
	// Body is the pull request description.
	Body string `json:"body"`
	// Head is the source branch name.
	Head string `json:"head"`
	// Base is the target branch name.
	Base string `json:"base"`
	// State is the current PR state (open, closed, merged).
	State string `json:"state"`
	// IssueNumber is the number of the issue this PR addresses.
	IssueNumber int `json:"issue_number,omitempty"`
	// FilesChanged lists the repository-relative paths of modified files.
	FilesChanged []string `json:"files_changed,omitempty"`
}

GitHubPREntity represents a GitHub pull request as a graph node. It implements the Graphable interface and records the relationship to the issue it fixes as a triple linking the two entities by their IDs.

func (*GitHubPREntity) EntityID

func (e *GitHubPREntity) EntityID() string

EntityID returns the 6-part federated identifier for this pull request. Format: {org}.github.repo.{repo}.pr.{number}

func (*GitHubPREntity) MarshalJSON

func (e *GitHubPREntity) MarshalJSON() ([]byte, error)

MarshalJSON implements json.Marshaler using the alias pattern to avoid recursion.

func (*GitHubPREntity) Schema

func (e *GitHubPREntity) Schema() message.Type

Schema returns the message type for GitHubPREntity payloads.

func (*GitHubPREntity) Triples

func (e *GitHubPREntity) Triples() []message.Triple

Triples returns semantic facts about this pull request. When IssueNumber is set, a "github.pr.fixes" triple is added that references the linked issue entity by its ID, enabling graph traversal from PR to issue.

func (*GitHubPREntity) UnmarshalJSON

func (e *GitHubPREntity) UnmarshalJSON(data []byte) error

UnmarshalJSON implements json.Unmarshaler using the alias pattern to avoid recursion.

func (*GitHubPREntity) Validate

func (e *GitHubPREntity) Validate() error

Validate checks that the pull request entity has all required fields.

type GitHubReviewEntity

type GitHubReviewEntity struct {
	// Org is the GitHub organization or user that owns the repository.
	Org string `json:"org"`
	// Repo is the repository name within the organization.
	Repo string `json:"repo"`
	// ID is a unique identifier for this review (e.g., UUID or sequential ID).
	ID string `json:"id"`
	// PRNumber is the pull request number that this review targets.
	PRNumber int `json:"pr_number"`
	// Verdict is the review outcome: approve, request_changes, or reject.
	Verdict string `json:"verdict"`
	// Issues is the count of issues found during review.
	Issues int `json:"issues"`
	// Agent is the role of the agent that performed this review (e.g., "reviewer").
	Agent string `json:"agent"`
}

GitHubReviewEntity represents a code review conducted by an agent on a pull request. It implements the Graphable interface and records the relationship to the reviewed PR as a "github.review.targets" triple.

func (*GitHubReviewEntity) EntityID

func (e *GitHubReviewEntity) EntityID() string

EntityID returns the 6-part federated identifier for this review. Format: {org}.github.repo.{repo}.review.{id}

func (*GitHubReviewEntity) MarshalJSON

func (e *GitHubReviewEntity) MarshalJSON() ([]byte, error)

MarshalJSON implements json.Marshaler using the alias pattern to avoid recursion.

func (*GitHubReviewEntity) Schema

func (e *GitHubReviewEntity) Schema() message.Type

Schema returns the message type for GitHubReviewEntity payloads.

func (*GitHubReviewEntity) Triples

func (e *GitHubReviewEntity) Triples() []message.Triple

Triples returns semantic facts about this review. A "github.review.targets" triple is always added when PRNumber is set, linking this review to its pull request entity by ID.

func (*GitHubReviewEntity) UnmarshalJSON

func (e *GitHubReviewEntity) UnmarshalJSON(data []byte) error

UnmarshalJSON implements json.Unmarshaler using the alias pattern to avoid recursion.

func (*GitHubReviewEntity) Validate

func (e *GitHubReviewEntity) Validate() error

Validate checks that the review entity has all required fields.

type IssueDetail

type IssueDetail struct {
	Number int    `json:"number"`
	Title  string `json:"title"`
	Body   string `json:"body"`
}

IssueDetail carries the issue fields required to bootstrap an execution.

type IssueToPRState

type IssueToPRState struct {
	reactive.ExecutionState

	// Issue context
	IssueNumber int    `json:"issue_number"`
	IssueTitle  string `json:"issue_title"`
	IssueBody   string `json:"issue_body"`
	RepoOwner   string `json:"repo_owner"`
	RepoName    string `json:"repo_name"`

	// Qualifier output
	QualifierVerdict    string  `json:"qualifier_verdict"`
	QualifierConfidence float64 `json:"qualifier_confidence"`
	Severity            string  `json:"severity"`

	// Developer output
	BranchName   string   `json:"branch_name"`
	PRNumber     int      `json:"pr_number"`
	PRUrl        string   `json:"pr_url"`
	FilesChanged []string `json:"files_changed"`

	// Reviewer output
	ReviewVerdict  string `json:"review_verdict"`
	ReviewFeedback string `json:"review_feedback"`

	// Adversarial tracking
	ReviewRejections    int  `json:"review_rejections"`
	DevelopmentAttempts int  `json:"development_attempts"`
	EscalatedToHuman    bool `json:"escalated_to_human"`

	// Cost tracking
	TotalTokensIn  int `json:"total_tokens_in"`
	TotalTokensOut int `json:"total_tokens_out"`
}

IssueToPRState tracks the execution state of the issue-to-PR workflow. It embeds ExecutionState to participate in the reactive engine's KV watch and async callback mechanisms.

func (*IssueToPRState) GetExecutionState

func (s *IssueToPRState) GetExecutionState() *reactive.ExecutionState

GetExecutionState implements reactive.StateAccessor to avoid reflection overhead.

type OwnerDetail

type OwnerDetail struct {
	Login string `json:"login"`
}

OwnerDetail carries the repository owner login.

type RepoDetail

type RepoDetail struct {
	Name  string      `json:"name"`
	Owner OwnerDetail `json:"owner"`
}

RepoDetail carries the repository owner and name.

type WorkflowCompletionPayload

type WorkflowCompletionPayload struct {
	ExecutionID         string   `json:"execution_id"`
	IssueNumber         int      `json:"issue_number"`
	RepoOwner           string   `json:"repo_owner"`
	RepoName            string   `json:"repo_name"`
	PRNumber            int      `json:"pr_number"`
	PRUrl               string   `json:"pr_url"`
	FilesChanged        []string `json:"files_changed"`
	DevelopmentAttempts int      `json:"development_attempts"`
	ReviewRejections    int      `json:"review_rejections"`
	TotalTokensIn       int      `json:"total_tokens_in"`
	TotalTokensOut      int      `json:"total_tokens_out"`
}

WorkflowCompletionPayload summarises the outcome of a completed issue-to-PR execution for downstream consumers.

func (*WorkflowCompletionPayload) MarshalJSON

func (p *WorkflowCompletionPayload) MarshalJSON() ([]byte, error)

MarshalJSON implements message.Payload.

func (*WorkflowCompletionPayload) Schema

Schema implements message.Payload.

func (*WorkflowCompletionPayload) UnmarshalJSON

func (p *WorkflowCompletionPayload) UnmarshalJSON(data []byte) error

UnmarshalJSON implements message.Payload.

func (*WorkflowCompletionPayload) Validate

func (p *WorkflowCompletionPayload) Validate() error

Validate implements message.Payload.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL