githubprworkflow

package
v1.0.0-alpha.45 Latest Latest
Warning

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

Go to latest
Published: Mar 14, 2026 License: MIT Imports: 14 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.

Note: GitHubIssueEntity, GitHubPREntity, and GitHubReviewEntity are published by the github-webhook input component when it ingests events from GitHub. The pr-workflow-spawner component in this package does not instantiate these entities directly — it writes workflow-phase triples via the graph mutation API instead.

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.
	DefaultTokenBudget = 500_000

	// DefaultMaxConcurrentWorkflows limits parallel active workflows.
	DefaultMaxConcurrentWorkflows = 3

	// DefaultHourlyTokenCeiling is the global hourly token limit across all workflows.
	DefaultHourlyTokenCeiling = 2_000_000

	// DefaultIssueCooldown is the minimum time between processing new issues.
	DefaultIssueCooldown = 60 * time.Second
)

Workflow constants.

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 as workflow.phase triples and drive all rule conditions.

View Source
const (
	// WorkflowSlug identifies this workflow for correlation in TaskMessage/LoopCompletedEvent.
	WorkflowSlug = "github-issue-to-pr"
)

Variables

This section is empty.

Functions

func BuildDeveloperPrompt

func BuildDeveloperPrompt(org, repo string, issueNumber int, issueTitle, issueBody, qualifierVerdict string, qualifierConfidence float64, severity, reviewFeedback string) string

BuildDeveloperPrompt builds the prompt for the developer agent. If reviewFeedback is non-empty, it is included as previous review feedback.

func BuildQualifierPrompt

func BuildQualifierPrompt(org, repo string, issueNumber int, title, body string) string

BuildQualifierPrompt builds the prompt for the qualifier agent.

func BuildReviewerPrompt

func BuildReviewerPrompt(org, repo string, issueNumber int, issueTitle string, prNumber int, prURL string, filesChanged []string) string

BuildReviewerPrompt builds the prompt for the reviewer agent.

func NewComponent

func NewComponent(rawConfig json.RawMessage, deps component.Dependencies) (component.Discoverable, error)

NewComponent creates a new PRWorkflowComponent from config.

func PhaseIsActive

func PhaseIsActive(phase string) bool

PhaseIsActive returns true if the phase indicates an active workflow.

func Register

func Register(registry *component.Registry) error

Register registers the PR workflow spawner component with the given registry.

func WorkflowEntityID

func WorkflowEntityID(org, repo string, issueNumber int) string

WorkflowEntityID builds the 6-part entity ID for a workflow execution.

Types

type Config

type Config struct {
	// Model is the model endpoint name for agent tasks (default: "default")
	Model string `json:"model,omitempty"`

	// TokenBudget is the maximum tokens per workflow execution (default: 500000)
	TokenBudget int `json:"token_budget,omitempty"`

	// MaxReviewCycles is the maximum review rejection/retry loops (default: 3)
	MaxReviewCycles int `json:"max_review_cycles,omitempty"`

	// Ports defines the component's input and output ports
	Ports PortConfig `json:"ports,omitempty"`
}

Config holds configuration for the PR workflow spawner component.

type DeveloperOutput

type DeveloperOutput struct {
	BranchName   string   `json:"branch_name"`
	PRNumber     int      `json:"pr_number"`
	PRUrl        string   `json:"pr_url"`
	FilesChanged []string `json:"files_changed"`
}

DeveloperOutput represents the parsed developer agent result.

func ParseDeveloperResult

func ParseDeveloperResult(result string) DeveloperOutput

ParseDeveloperResult parses the developer agent's JSON result.

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.

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 OwnerDetail

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

OwnerDetail carries the repository owner login.

type PRWorkflowComponent

type PRWorkflowComponent struct {
	// contains filtered or unexported fields
}

PRWorkflowComponent spawns agent tasks for the GitHub issue-to-PR pipeline. It handles the Go-intensive parts of the workflow: prompt building, result parsing, and triple writing. The simpler reactive patterns (phase transitions, budget checks) are handled by JSON rules in configs/rules/github-pr-workflow/.

func (*PRWorkflowComponent) ConfigSchema

func (c *PRWorkflowComponent) ConfigSchema() component.ConfigSchema

ConfigSchema returns the JSON schema for this component's configuration. The canonical schema definition lives in register.go as prWorkflowSchema.

func (*PRWorkflowComponent) DataFlow

DataFlow returns current flow metrics for the component.

func (*PRWorkflowComponent) Health

Health returns the current health status of the component.

func (*PRWorkflowComponent) Initialize

func (c *PRWorkflowComponent) Initialize() error

Initialize prepares the component.

func (*PRWorkflowComponent) InputPorts

func (c *PRWorkflowComponent) InputPorts() []component.Port

InputPorts returns the component's input port definitions.

func (*PRWorkflowComponent) Meta

Meta returns component metadata.

func (*PRWorkflowComponent) OutputPorts

func (c *PRWorkflowComponent) OutputPorts() []component.Port

OutputPorts returns the component's output port definitions.

func (*PRWorkflowComponent) Start

func (c *PRWorkflowComponent) Start(ctx context.Context) error

Start begins consuming events and agent completions.

func (*PRWorkflowComponent) Stop

Stop performs graceful shutdown.

type PortConfig

type PortConfig struct {
	Inputs  []PortDef `json:"inputs,omitempty"`
	Outputs []PortDef `json:"outputs,omitempty"`
}

PortConfig defines port configuration.

type PortDef

type PortDef struct {
	Name    string `json:"name"`
	Subject string `json:"subject"`
	Type    string `json:"type"`
	Stream  string `json:"stream,omitempty"`
}

PortDef defines a single port.

type QualifierVerdict

type QualifierVerdict struct {
	Verdict    string  `json:"verdict"`
	Confidence float64 `json:"confidence"`
	Severity   string  `json:"severity"`
}

QualifierVerdict represents the parsed qualifier agent result.

func ParseQualifierResult

func ParseQualifierResult(result string) QualifierVerdict

ParseQualifierResult parses the qualifier agent's JSON result. Returns a verdict with "needs_info" on parse failure.

type RepoDetail

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

RepoDetail carries the repository owner and name.

type ReviewerOutput

type ReviewerOutput struct {
	Verdict  string `json:"verdict"`
	Feedback string `json:"feedback"`
}

ReviewerOutput represents the parsed reviewer agent result.

func ParseReviewerResult

func ParseReviewerResult(result string) ReviewerOutput

ParseReviewerResult parses the reviewer agent's JSON result.

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