feat(mrqueue): Add MQ priority objective function (gt-si8rq.1)

Implement ScoreMR function for merge queue priority ordering with:
- Convoy age factor (prevents starvation of old convoys)
- Priority factor (P0 beats P4)
- Retry penalty (prevents thrashing on conflict-prone MRs)
- MR age tiebreaker (FIFO within same priority)

Added fields to MR struct:
- RetryCount for conflict retry tracking
- ConvoyID and ConvoyCreatedAt for convoy linkage

Includes comprehensive unit tests and documentation.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
furiosa
2026-01-02 01:26:03 -08:00
committed by Steve Yegge
parent 87d79c40ee
commit 17fd366888
3 changed files with 586 additions and 0 deletions

View File

@@ -28,6 +28,11 @@ type MR struct {
CreatedAt time.Time `json:"created_at"`
AgentBead string `json:"agent_bead,omitempty"` // Agent bead ID that created this MR (for traceability)
// Priority scoring fields
RetryCount int `json:"retry_count,omitempty"` // Conflict retry count for priority penalty
ConvoyID string `json:"convoy_id,omitempty"` // Parent convoy ID if part of a convoy
ConvoyCreatedAt *time.Time `json:"convoy_created_at,omitempty"` // Convoy creation time for starvation prevention
// Claiming fields for parallel refinery workers
ClaimedBy string `json:"claimed_by,omitempty"` // Worker ID that claimed this MR
ClaimedAt *time.Time `json:"claimed_at,omitempty"` // When the MR was claimed
@@ -102,6 +107,7 @@ func (q *Queue) Submit(mr *MR) error {
}
// List returns all pending MRs, sorted by priority then creation time.
// Deprecated: Use ListByScore for priority-aware ordering.
func (q *Queue) List() ([]*MR, error) {
entries, err := os.ReadDir(q.dir)
if err != nil {
@@ -135,6 +141,43 @@ func (q *Queue) List() ([]*MR, error) {
return mrs, nil
}
// ListByScore returns all pending MRs sorted by priority score (highest first).
// Uses the ScoreMR function which considers:
// - Convoy age (prevents starvation)
// - Issue priority (P0-P4)
// - Retry count (prevents thrashing)
// - MR age (FIFO tiebreaker)
func (q *Queue) ListByScore() ([]*MR, error) {
entries, err := os.ReadDir(q.dir)
if err != nil {
if os.IsNotExist(err) {
return nil, nil // Empty queue
}
return nil, fmt.Errorf("reading mq directory: %w", err)
}
now := time.Now()
var mrs []*MR
for _, entry := range entries {
if entry.IsDir() || !strings.HasSuffix(entry.Name(), ".json") {
continue
}
mr, err := q.load(filepath.Join(q.dir, entry.Name()))
if err != nil {
continue // Skip malformed files
}
mrs = append(mrs, mr)
}
// Sort by score (higher first = higher priority)
sort.Slice(mrs, func(i, j int) bool {
return mrs[i].ScoreAt(now) > mrs[j].ScoreAt(now)
})
return mrs, nil
}
// Get retrieves a specific MR by ID.
func (q *Queue) Get(id string) (*MR, error) {
path := filepath.Join(q.dir, id+".json")