From 9ecb5c9b476c3edee92f302e49184b47d16bf8b0 Mon Sep 17 00:00:00 2001 From: DBT Date: Tue, 24 Feb 2026 12:30:26 +0000 Subject: [PATCH] support explicit [keys: ...] resource hints for parallel autonomy conflict control --- README.md | 8 +++++++ README_EN.md | 8 +++++++ pkg/autonomy/engine.go | 53 ++++++++++++++++++++++++++++++++++++++---- 3 files changed, 65 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 4fb59c6..ed8ab91 100644 --- a/README.md +++ b/README.md @@ -35,6 +35,14 @@ 这些优化提升了高并发场景下的稳定性、可观测性与可维护性。 +### 并行任务冲突控制(Autonomy) + +支持基于 `resource_keys` 的锁调度。任务可在内容中显式声明资源键,提升并行判冲突精度: + +- 示例:`[keys: repo:clawgo, file:pkg/agent/loop.go, branch:main] 修复对话流程` +- 未显式声明时,系统会从任务文本自动推断资源键。 +- 冲突任务进入 `resource_lock` 等待,默认 30 秒后重试抢锁,并带公平加权(等待越久优先级越高)。 + ## 🏁 快速开始 1. 初始化配置与工作区 diff --git a/README_EN.md b/README_EN.md index 5a612b6..ac98a73 100644 --- a/README_EN.md +++ b/README_EN.md @@ -35,6 +35,14 @@ A recent architecture pass leveraged core Go strengths: These changes improve stability, observability, and maintainability under concurrency. +### Parallel task conflict control (Autonomy) + +Autonomy now supports lock scheduling via `resource_keys`. You can explicitly declare keys in task text for precise conflict detection: + +- Example: `[keys: repo:clawgo, file:pkg/agent/loop.go, branch:main] fix dialog flow` +- Without explicit keys, the engine derives keys from task text heuristically. +- Conflicting tasks enter `resource_lock` waiting, retry lock acquisition after 30s, and use fairness weighting (longer wait => higher scheduling priority). + ## 🏁 Quick Start 1. Initialize config and workspace diff --git a/pkg/autonomy/engine.go b/pkg/autonomy/engine.go index 6b1b8ff..6af10a3 100644 --- a/pkg/autonomy/engine.go +++ b/pkg/autonomy/engine.go @@ -353,10 +353,14 @@ func schedulingScore(st *taskState, now time.Time) int { } func deriveResourceKeys(content string) []string { - content = strings.TrimSpace(strings.ToLower(content)) - if content == "" { + raw := strings.TrimSpace(content) + if raw == "" { return nil } + if explicit := parseExplicitResourceKeys(raw); len(explicit) > 0 { + return explicit + } + content = strings.ToLower(raw) keys := make([]string, 0, 8) hasRepo := false for _, token := range strings.Fields(content) { @@ -380,10 +384,51 @@ func deriveResourceKeys(content string) []string { if len(keys) == 0 { keys = append(keys, "scope:general") } + return normalizeResourceKeys(keys) +} + +func parseExplicitResourceKeys(content string) []string { + lower := strings.ToLower(content) + start := strings.Index(lower, "[keys:") + if start < 0 { + return nil + } + rest := content[start+6:] + end := strings.Index(rest, "]") + if end < 0 { + return nil + } + body := strings.TrimSpace(rest[:end]) + if body == "" { + return nil + } + parts := strings.Split(body, ",") + keys := make([]string, 0, len(parts)) + for _, p := range parts { + k := strings.ToLower(strings.TrimSpace(p)) + if k == "" { + continue + } + if !strings.Contains(k, ":") { + k = "file:" + k + } + keys = append(keys, k) + } + return normalizeResourceKeys(keys) +} + +func normalizeResourceKeys(keys []string) []string { + if len(keys) == 0 { + return nil + } sort.Strings(keys) uniq := keys[:0] - for i, k := range keys { - if i == 0 || k != keys[i-1] { + for _, k := range keys { + k = strings.TrimSpace(strings.ToLower(k)) + if k == "" { + continue + } + if len(uniq) == 0 || k != uniq[len(uniq)-1] { uniq = append(uniq, k) } }