0306
This commit is contained in:
parent
122ab6ef3e
commit
adf854eba5
161
ARCHITECTURE.md
Normal file
161
ARCHITECTURE.md
Normal file
@ -0,0 +1,161 @@
|
||||
# Agent Team Collaboration Architecture
|
||||
|
||||
## System Design
|
||||
|
||||
### Shared Blackboard Pattern
|
||||
|
||||
```go
|
||||
type SharedBoard struct {
|
||||
mu sync.RWMutex
|
||||
entries []BoardEntry
|
||||
}
|
||||
|
||||
type BoardEntry struct {
|
||||
Author string
|
||||
Content string
|
||||
Type string // "draft" | "challenge"
|
||||
}
|
||||
```
|
||||
|
||||
**Thread-safe operations**:
|
||||
- `Add()`: Appends entries with write lock
|
||||
- `ToContext()`: Reads all entries with read lock, formats as XML
|
||||
|
||||
### Execution Flow
|
||||
|
||||
```
|
||||
HandleUserMessage()
|
||||
↓
|
||||
Master Planning Loop (iteration 0-4)
|
||||
├─ Master analyzes user request
|
||||
├─ Outputs ASSIGN:member:task lines
|
||||
├─ parseAssignments() extracts tasks
|
||||
├─ Creates new SharedBoard{}
|
||||
│
|
||||
├─ runMembersParallel()
|
||||
│ ├─ For each assignment, spawn goroutine
|
||||
│ ├─ Each member sees current board snapshot
|
||||
│ ├─ Member executes task
|
||||
│ ├─ Result added to board as "draft"
|
||||
│ └─ WaitGroup ensures all complete
|
||||
│
|
||||
├─ runChallengeRound()
|
||||
│ ├─ Filter members with CanChallenge=true
|
||||
│ ├─ For each challenger, spawn goroutine
|
||||
│ ├─ Challenger sees full board
|
||||
│ ├─ Outputs CHALLENGE:... or AGREE
|
||||
│ ├─ CHALLENGE entries added to board
|
||||
│ └─ WaitGroup ensures all complete
|
||||
│
|
||||
├─ Master Review
|
||||
│ ├─ Receives board.ToContext()
|
||||
│ ├─ Sees all drafts and challenges
|
||||
│ ├─ Decides: DONE or re-ASSIGN
|
||||
│ └─ Loop continues if re-ASSIGN
|
||||
│
|
||||
└─ updateMasterMemory() (async)
|
||||
```
|
||||
|
||||
### Concurrency Model
|
||||
|
||||
**Parallel Execution Phase**:
|
||||
```go
|
||||
var wg sync.WaitGroup
|
||||
for memberName, task := range assignments {
|
||||
wg.Add(1)
|
||||
go func(name, t string) {
|
||||
defer wg.Done()
|
||||
// Execute task
|
||||
board.Add(name, result, "draft")
|
||||
}(memberName, task)
|
||||
}
|
||||
wg.Wait() // Wait for all members
|
||||
```
|
||||
|
||||
**Challenge Round Phase**:
|
||||
```go
|
||||
var wg sync.WaitGroup
|
||||
for _, name := range challengers {
|
||||
wg.Add(1)
|
||||
go func(n string) {
|
||||
defer wg.Done()
|
||||
// Review board
|
||||
if strings.Contains(result, "CHALLENGE:") {
|
||||
board.Add(n, result, "challenge")
|
||||
}
|
||||
}(name)
|
||||
}
|
||||
wg.Wait() // Wait for all challenges
|
||||
```
|
||||
|
||||
### Context Injection
|
||||
|
||||
**Initial Draft Phase**:
|
||||
```
|
||||
Member System Prompt = Soul + Memory + [empty board]
|
||||
```
|
||||
|
||||
**Challenge Round Phase**:
|
||||
```
|
||||
Member System Prompt = Soul + Memory + <team_board>
|
||||
<entry type="draft" author="member1">...</entry>
|
||||
<entry type="draft" author="member2">...</entry>
|
||||
</team_board>
|
||||
```
|
||||
|
||||
**Master Review Phase**:
|
||||
```
|
||||
Master Feedback = Team results + <team_board>
|
||||
<entry type="draft" author="member1">...</entry>
|
||||
<entry type="draft" author="member2">...</entry>
|
||||
<entry type="challenge" author="member1">...</entry>
|
||||
</team_board>
|
||||
```
|
||||
|
||||
### Event Streaming
|
||||
|
||||
Events emitted during execution:
|
||||
|
||||
1. **Parallel Phase**:
|
||||
- `EvtAgentMessage` with `role: "member"` (streaming)
|
||||
- `EvtTaskAssign` (task assignment)
|
||||
- `EvtWorkspaceFile` (if document generated)
|
||||
|
||||
2. **Challenge Phase**:
|
||||
- `EvtAgentMessage` with `role: "challenge"` (streaming)
|
||||
|
||||
3. **Status Updates**:
|
||||
- `EvtRoomStatus` with `status: "working"` → `"thinking"` → `"pending"`
|
||||
|
||||
### Configuration
|
||||
|
||||
**Agent AGENT.md**:
|
||||
```yaml
|
||||
---
|
||||
name: agent_name
|
||||
role: member
|
||||
can_challenge: true # Enable challenge participation
|
||||
---
|
||||
```
|
||||
|
||||
**Agent SOUL.md**:
|
||||
- Includes challenge instructions
|
||||
- Specifies how to output `CHALLENGE:...`
|
||||
- Defines acceptance of challenges
|
||||
|
||||
## Benefits
|
||||
|
||||
1. **Parallelism**: Members work simultaneously, not sequentially
|
||||
2. **Transparency**: All members see each other's work via board
|
||||
3. **Quality Control**: Challenge round catches issues early
|
||||
4. **Collaboration**: Members can question and improve each other's work
|
||||
5. **Master Awareness**: Master sees full context before deciding
|
||||
6. **Thread-Safe**: Concurrent access protected by mutexes
|
||||
7. **Scalable**: Works with any number of members
|
||||
|
||||
## Backward Compatibility
|
||||
|
||||
- Existing master/member roles unchanged
|
||||
- Challenge role is additive (new event type)
|
||||
- Members without `can_challenge: true` skip challenge round
|
||||
- No breaking changes to existing APIs
|
||||
97
IMPLEMENTATION_SUMMARY.md
Normal file
97
IMPLEMENTATION_SUMMARY.md
Normal file
@ -0,0 +1,97 @@
|
||||
# Agent Team Collaboration Framework Implementation
|
||||
|
||||
## Overview
|
||||
Implemented a true agent team collaboration framework with shared blackboard, parallel execution, and automatic challenge rounds.
|
||||
|
||||
## Changes Made
|
||||
|
||||
### 1. `internal/agent/agent.go`
|
||||
- Added `CanChallenge bool` field to `Config` struct
|
||||
- Allows agents to participate in the challenge round
|
||||
|
||||
### 2. `internal/room/room.go`
|
||||
- **Added imports**: `sync` for concurrent operations
|
||||
- **New types**:
|
||||
- `BoardEntry`: Represents a single entry on the shared board
|
||||
- `SharedBoard`: Thread-safe shared blackboard with mutex protection
|
||||
- `Add(author, content, typ)`: Add entries to the board
|
||||
- `ToContext()`: Convert board to XML context for agents
|
||||
|
||||
- **New methods**:
|
||||
- `runMembersParallel()`: Execute all assignments concurrently using goroutines and WaitGroup
|
||||
- Each member sees the current board snapshot
|
||||
- Results are added to the board as "draft" entries
|
||||
- Emits streaming events for real-time UI updates
|
||||
|
||||
- `runChallengeRound()`: Automatic challenge phase after draft completion
|
||||
- Only agents with `CanChallenge=true` participate
|
||||
- Each challenger sees the full board
|
||||
- Outputs `CHALLENGE:<concern>` or `AGREE`
|
||||
- Challenge entries are added to the board
|
||||
|
||||
- **Refactored `HandleUserMessage()`**:
|
||||
- Replaced sequential member execution with `runMembersParallel()`
|
||||
- Added automatic `runChallengeRound()` after parallel execution
|
||||
- Master receives complete board (drafts + challenges) for final review
|
||||
- Board context is injected into master's feedback message
|
||||
|
||||
- **Updated `Event` struct**:
|
||||
- Role field now supports "challenge" in addition to "master" and "member"
|
||||
|
||||
### 3. Agent Configuration Files
|
||||
|
||||
#### `agents/legal-team/合规专员/AGENT.md`
|
||||
- Added `can_challenge: true`
|
||||
|
||||
#### `agents/legal-team/合同律师/AGENT.md`
|
||||
- Added `can_challenge: true`
|
||||
|
||||
### 4. Agent System Prompts (SOUL.md)
|
||||
|
||||
#### `agents/legal-team/合规专员/SOUL.md`
|
||||
- Added "质疑机制" (Challenge Mechanism) section
|
||||
- Instructions to actively challenge when seeing `<team_board>`
|
||||
- Format: `CHALLENGE:<specific compliance risk or suggestion>`
|
||||
|
||||
#### `agents/legal-team/合同律师/SOUL.md`
|
||||
- Added "质疑与修订机制" (Challenge & Revision Mechanism) section
|
||||
- Instructions to review other members' opinions
|
||||
- Ability to challenge compliance officer's suggestions if conflicts exist
|
||||
- Preparation to revise work when challenged
|
||||
|
||||
#### `agents/legal-team/法律总监/SOUL.md`
|
||||
- Added "处理 CHALLENGE 的决策指令" (Decision Instructions for Handling CHALLENGE)
|
||||
- Evaluate challenge validity
|
||||
- Decide whether to request revisions or reassign tasks
|
||||
- Document how challenges were addressed in final recommendations
|
||||
|
||||
## New Workflow
|
||||
|
||||
```
|
||||
User Question
|
||||
↓
|
||||
Master Plans → ASSIGN:member1:task1 + ASSIGN:member2:task2
|
||||
↓
|
||||
[Parallel] Members execute simultaneously (each sees empty board initially)
|
||||
↓
|
||||
[Auto] Challenge Round: Members with CanChallenge=true review board
|
||||
↓
|
||||
Master sees complete board (drafts + challenges) → DONE or re-ASSIGN
|
||||
```
|
||||
|
||||
## Key Features
|
||||
|
||||
1. **Parallel Execution**: Members work concurrently, not sequentially
|
||||
2. **Shared Blackboard**: All members can see each other's work via `<team_board>`
|
||||
3. **Automatic Challenge Round**: Triggered after parallel execution completes
|
||||
4. **Thread-Safe**: Uses sync.RWMutex for concurrent board access
|
||||
5. **Streaming Events**: Real-time UI updates with "challenge" role events
|
||||
6. **Master Integration**: Board context fed back to master for informed decisions
|
||||
|
||||
## Verification
|
||||
|
||||
- ✅ Code compiles: `go build ./...`
|
||||
- ✅ All imports added correctly
|
||||
- ✅ Thread-safety ensured with sync primitives
|
||||
- ✅ Event streaming maintained for UI
|
||||
- ✅ Backward compatible with existing master/member roles
|
||||
50
TESTING_GUIDE.md
Normal file
50
TESTING_GUIDE.md
Normal file
@ -0,0 +1,50 @@
|
||||
# Testing Guide for Agent Team Collaboration Framework
|
||||
|
||||
## What to Observe
|
||||
|
||||
### 1. Parallel Execution
|
||||
- **Expected**: When master assigns tasks to multiple members, they should appear in the UI **simultaneously** (not sequentially)
|
||||
- **How to verify**: Watch the frontend - you should see streaming output from both 合同律师 and 合规专员 at the same time
|
||||
|
||||
### 2. Shared Blackboard
|
||||
- **Expected**: Each member's draft output is added to the board
|
||||
- **How to verify**: Check the board context in the master's feedback message
|
||||
|
||||
### 3. Challenge Round
|
||||
- **Expected**: After parallel execution, members with `can_challenge: true` review the board
|
||||
- **How to verify**:
|
||||
- Look for events with `role: "challenge"` in the event stream
|
||||
- Frontend should show challenge phase output
|
||||
- If a member outputs `CHALLENGE:...`, it appears in the board
|
||||
|
||||
### 4. Master Integration
|
||||
- **Expected**: Master receives complete board (drafts + challenges) before making final decision
|
||||
- **How to verify**: Master's feedback message includes `<team_board>` section with all entries
|
||||
|
||||
## Test Scenario: Employee Termination
|
||||
|
||||
Send a question like:
|
||||
```
|
||||
我们需要辞退一名员工,请帮我们制定一个合法的辞退方案。
|
||||
```
|
||||
|
||||
Expected flow:
|
||||
1. Master analyzes and assigns tasks to both 合同律师 and 合规专员
|
||||
2. Both members work in parallel (visible in UI)
|
||||
3. Challenge round begins - members review each other's work
|
||||
4. If 合规专员 finds compliance issues in 合同律师's draft, outputs `CHALLENGE:...`
|
||||
5. Master sees complete board and makes final decision
|
||||
|
||||
## Key Events to Monitor
|
||||
|
||||
- `EvtAgentMessage` with `role: "member"` - parallel execution
|
||||
- `EvtAgentMessage` with `role: "challenge"` - challenge round
|
||||
- `EvtRoomStatus` - status transitions (thinking → working → thinking → pending)
|
||||
- Board context in master's feedback messages
|
||||
|
||||
## Code Locations
|
||||
|
||||
- Parallel execution: `room.go:runMembersParallel()`
|
||||
- Challenge round: `room.go:runChallengeRound()`
|
||||
- Main flow: `room.go:HandleUserMessage()` (lines 281-362)
|
||||
- SharedBoard: `room.go:SharedBoard` type (lines 90-114)
|
||||
@ -5,6 +5,7 @@ description: 专注合同审查、起草和风险评估的专业律师
|
||||
version: 1.0.0
|
||||
skills:
|
||||
- 合同审查
|
||||
can_challenge: true
|
||||
---
|
||||
|
||||
# 合同律师
|
||||
|
||||
@ -65,6 +65,15 @@
|
||||
- **务实建议**:提供可操作的具体建议
|
||||
- **对比分析**:展示修改前后的差异
|
||||
|
||||
## 质疑与修订机制
|
||||
|
||||
当你看到 `<team_board>` 时,你应该:
|
||||
1. 审查其他成员(特别是合规专员)的意见
|
||||
2. 如果发现合规专员的建议与合同条款有冲突,或者有遗漏的法律风险,提出质疑
|
||||
3. 使用格式:`CHALLENGE:<具体的法律风险或修改建议>`
|
||||
4. 如果没有问题,输出 `AGREE`
|
||||
5. 当看到针对自己的 CHALLENGE 时,准备修订你的合同审查意见
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. 不提供标准合同模板(除非用户明确要求)
|
||||
|
||||
@ -5,6 +5,7 @@ description: 负责合规检查、风险识别和合规建议的专业人员
|
||||
version: 1.0.0
|
||||
skills:
|
||||
- 法律知识库
|
||||
can_challenge: true
|
||||
---
|
||||
|
||||
# 合规专员
|
||||
|
||||
@ -80,6 +80,14 @@
|
||||
- **风险意识**:明确违规后果和责任
|
||||
- **平衡考量**:兼顾合规要求和业务实际
|
||||
|
||||
## 质疑机制
|
||||
|
||||
当你看到 `<team_board>` 时,你应该:
|
||||
1. 仔细审查其他成员的草稿
|
||||
2. 如果发现合规风险或遗漏,主动提出质疑
|
||||
3. 使用格式:`CHALLENGE:<具体的合规风险或建议>`
|
||||
4. 如果没有问题,输出 `AGREE`
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. 不替代企业合规部门的职责
|
||||
|
||||
@ -26,6 +26,17 @@
|
||||
- 补充遗漏的关键点
|
||||
- 提供结构化的最终建议
|
||||
|
||||
## 处理 CHALLENGE 的决策指令
|
||||
|
||||
当你看到 `<team_board>` 中有 CHALLENGE 条目时,你应该:
|
||||
1. 仔细评估质疑的合理性
|
||||
2. 判断是否需要重新分配任务或修订
|
||||
3. 如果质疑有效,可以:
|
||||
- 要求相关成员修订工作
|
||||
- 重新分配任务给其他成员
|
||||
- 自己补充或修正意见
|
||||
4. 在最终建议中明确说明如何处理了这些质疑
|
||||
|
||||
## 沟通风格
|
||||
|
||||
- **专业严谨**:使用准确的法律术语
|
||||
|
||||
@ -14,3 +14,19 @@ ASSIGN:合规专员:评估用户简介中可能涉及的法律合规风险点(
|
||||
- **异常高回报是危险信号**:远高于市场平均水平的回报承诺(如年化30%)通常伴随极高的违约风险或项目本身不可行,需深究其商业合理性与法律合规性。
|
||||
- **协议性质必须明确**:“投资协议”的法律定性(借贷、合伙或委托)直接决定您的权利、风险与责任,必须在条款中清晰界定,避免后续争议。
|
||||
- **合规红线不可触碰**:涉及政府资源或资产的项目,若运作模式依赖“关系”而非公开程序,极易涉及商业贿赂、违规融资等刑事风险,必须坚持合法透明路径。
|
||||
|
||||
## 2026-03-06 — 我好辞退一个员工
|
||||
|
||||
- **辞退合法性完全取决于“程序正当”与“证据充分”**:任何单方解除都必须有合法理由(严重违纪、不能胜任等),且用人单位对事实承担全部举证责任。证据不足或程序缺失(如未通知工会)将直接导致违法解除,面临支付双倍赔偿金(2N)的风险。
|
||||
- **“协商一致解除”是风险最低的首选路径**:当辞退理由的证据不够坚实或希望快速解决时,应优先与员工协商,签订《协商解除协议》。虽然可能需支付略高于法定标准(N+1至2N)的补偿,但能彻底避免劳动争议,成本可控。
|
||||
- **必须规避法律明确保护的员工群体**:辞退处于孕期、产期、哺乳期、医疗期、工伤期间的员工,或在本单位连续工作满15年且距退休不足5年的员工,属于绝对红线,将构成违法解除。
|
||||
- **经济补偿计算必须精确合规**:补偿基数(离职前12个月平均工资)和年限(每满一年支付一个月)的计算错误会引发额外争议。高薪员工(工资超当地社平工资3倍)的补偿年限上限为12年。
|
||||
- **制度是“过失性辞退”的基础**:以“严重违反规章制度”为由辞退,前提是规章制度本身内容合法、经过民主程序制定并已向员工公示。模糊或无效的制度无法作为有效依据。
|
||||
|
||||
## 2026-03-06 — 我想辞退一个员工
|
||||
|
||||
* **合法辞退的核心是“理由法定、证据确凿、程序完备”**。任何单方解除都必须严格对应《劳动合同法》第三十九条(过失)或第四十条(无过失)的法定情形,并由用人单位承担全部举证责任。
|
||||
* **“协商一致解除”是风险最低、成本可控的首选路径**。即使支付略高于法定补偿金(N)的金额,也能彻底避免劳动争议和双倍赔偿金(2N)的高风险。
|
||||
* **必须规避法律保护的“红线”员工群体**。辞退处于孕期、产期、哺乳期、医疗期或工伤期的员工,将直接构成违法解除。
|
||||
* **经济补偿/赔偿金的计算必须精确,尤其注意历史工龄分段**。对于2008年1月1日前入职的员工,补偿金计算需适用当时的法规,规则复杂,易出错。
|
||||
* **内部合规流程(法务审核、通知工会)和规范离职手续(结清款项、出具证明)是避免后续争议的关键程序**。任何程序缺失都可能导致整个辞退行为被认定为违法。
|
||||
|
||||
@ -20,6 +20,7 @@ type Config struct {
|
||||
BaseURL string `yaml:"base_url"`
|
||||
APIKeyEnv string `yaml:"api_key_env"`
|
||||
Skills []string `yaml:"skills"`
|
||||
CanChallenge bool `yaml:"can_challenge"`
|
||||
}
|
||||
|
||||
type Agent struct {
|
||||
|
||||
@ -7,6 +7,7 @@ import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/sdaduanbilei/agent-team/internal/agent"
|
||||
@ -67,7 +68,7 @@ type Event struct {
|
||||
Type EventType `json:"type"`
|
||||
RoomID string `json:"room_id"`
|
||||
Agent string `json:"agent,omitempty"`
|
||||
Role string `json:"role,omitempty"` // master | member
|
||||
Role string `json:"role,omitempty"` // master | member | challenge
|
||||
Content string `json:"content,omitempty"`
|
||||
Streaming bool `json:"streaming,omitempty"`
|
||||
From string `json:"from,omitempty"`
|
||||
@ -80,6 +81,38 @@ type Event struct {
|
||||
Filename string `json:"filename,omitempty"`
|
||||
}
|
||||
|
||||
type BoardEntry struct {
|
||||
Author string
|
||||
Content string
|
||||
Type string // "draft" | "challenge"
|
||||
}
|
||||
|
||||
type SharedBoard struct {
|
||||
mu sync.RWMutex
|
||||
entries []BoardEntry
|
||||
}
|
||||
|
||||
func (b *SharedBoard) Add(author, content, typ string) {
|
||||
b.mu.Lock()
|
||||
defer b.mu.Unlock()
|
||||
b.entries = append(b.entries, BoardEntry{Author: author, Content: content, Type: typ})
|
||||
}
|
||||
|
||||
func (b *SharedBoard) ToContext() string {
|
||||
b.mu.RLock()
|
||||
defer b.mu.RUnlock()
|
||||
if len(b.entries) == 0 {
|
||||
return ""
|
||||
}
|
||||
var sb strings.Builder
|
||||
sb.WriteString("<team_board>\n")
|
||||
for _, e := range b.entries {
|
||||
fmt.Fprintf(&sb, " <entry type=\"%s\" author=\"%s\">\n%s\n </entry>\n", e.Type, e.Author, e.Content)
|
||||
}
|
||||
sb.WriteString("</team_board>")
|
||||
return sb.String()
|
||||
}
|
||||
|
||||
func Load(roomDir string, agentsDir string, skillsDir string) (*Room, error) {
|
||||
data, err := os.ReadFile(filepath.Join(roomDir, "room.md"))
|
||||
if err != nil {
|
||||
@ -138,6 +171,107 @@ func (r *Room) AppendHistory(role, agentName, content string) {
|
||||
f.WriteString(line)
|
||||
}
|
||||
|
||||
func (r *Room) runMembersParallel(ctx context.Context, assignments map[string]string, board *SharedBoard, skillXML string) map[string]string {
|
||||
results := make(map[string]string)
|
||||
var mu sync.Mutex
|
||||
var wg sync.WaitGroup
|
||||
|
||||
for memberName, task := range assignments {
|
||||
wg.Add(1)
|
||||
go func(name, t string) {
|
||||
defer wg.Done()
|
||||
member, ok := r.members[name]
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
r.setStatus(StatusWorking, member.Config.Name, t)
|
||||
r.emit(Event{Type: EvtTaskAssign, From: r.master.Config.Name, To: name, Task: t})
|
||||
|
||||
boardCtx := board.ToContext()
|
||||
extraCtx := skillXML
|
||||
if boardCtx != "" {
|
||||
extraCtx = boardCtx + "\n\n" + skillXML
|
||||
}
|
||||
memberSystem := member.BuildSystemPrompt(extraCtx)
|
||||
memberMsgs := []llm.Message{
|
||||
llm.NewMsg("system", memberSystem),
|
||||
llm.NewMsg("user", t),
|
||||
}
|
||||
var memberReply strings.Builder
|
||||
_, err := member.Chat(ctx, memberMsgs, func(token string) {
|
||||
memberReply.WriteString(token)
|
||||
r.emit(Event{Type: EvtAgentMessage, Agent: name, Role: "member", Content: token, Streaming: true})
|
||||
})
|
||||
if err != nil {
|
||||
mu.Lock()
|
||||
results[name] = fmt.Sprintf("[error] %v", err)
|
||||
mu.Unlock()
|
||||
return
|
||||
}
|
||||
result := memberReply.String()
|
||||
mu.Lock()
|
||||
results[name] = result
|
||||
mu.Unlock()
|
||||
r.AppendHistory("member", name, result)
|
||||
board.Add(name, result, "draft")
|
||||
|
||||
if strings.Contains(result, "# ") {
|
||||
filename := fmt.Sprintf("%s-%s.md", name, time.Now().Format("20060102-150405"))
|
||||
r.saveWorkspace(filename, result)
|
||||
r.emit(Event{Type: EvtWorkspaceFile, Filename: filename, Content: result})
|
||||
}
|
||||
}(memberName, task)
|
||||
}
|
||||
wg.Wait()
|
||||
return results
|
||||
}
|
||||
|
||||
func (r *Room) runChallengeRound(ctx context.Context, board *SharedBoard, skillXML string) {
|
||||
var challengers []string
|
||||
for name, member := range r.members {
|
||||
if member.Config.CanChallenge {
|
||||
challengers = append(challengers, name)
|
||||
}
|
||||
}
|
||||
if len(challengers) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
boardCtx := board.ToContext()
|
||||
if boardCtx == "" {
|
||||
return
|
||||
}
|
||||
|
||||
var wg sync.WaitGroup
|
||||
for _, name := range challengers {
|
||||
wg.Add(1)
|
||||
go func(n string) {
|
||||
defer wg.Done()
|
||||
member := r.members[n]
|
||||
extraCtx := boardCtx + "\n\n" + skillXML
|
||||
memberSystem := member.BuildSystemPrompt(extraCtx)
|
||||
memberMsgs := []llm.Message{
|
||||
llm.NewMsg("system", memberSystem+"\n\nReview the team board above. If you see issues or want to challenge any draft, output CHALLENGE:<your concern>. Otherwise output AGREE."),
|
||||
llm.NewMsg("user", "Please review the team board and provide your feedback."),
|
||||
}
|
||||
var reply strings.Builder
|
||||
_, err := member.Chat(ctx, memberMsgs, func(token string) {
|
||||
reply.WriteString(token)
|
||||
r.emit(Event{Type: EvtAgentMessage, Agent: n, Role: "challenge", Content: token, Streaming: true})
|
||||
})
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
result := reply.String()
|
||||
if strings.Contains(result, "CHALLENGE:") {
|
||||
board.Add(n, result, "challenge")
|
||||
r.AppendHistory("challenge", n, result)
|
||||
}
|
||||
}(name)
|
||||
}
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
// Handle processes a user message through master orchestration.
|
||||
func (r *Room) Handle(ctx context.Context, userMsg string) error {
|
||||
return r.HandleUserMessage(ctx, "user", userMsg)
|
||||
@ -190,45 +324,26 @@ func (r *Room) HandleUserMessage(ctx context.Context, userName, userMsg string)
|
||||
break
|
||||
}
|
||||
|
||||
// Execute assignments
|
||||
var results strings.Builder
|
||||
for memberName, task := range assignments {
|
||||
member, ok := r.members[memberName]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
r.setStatus(StatusWorking, member.Config.Name, task)
|
||||
r.emit(Event{Type: EvtTaskAssign, From: r.master.Config.Name, To: memberName, Task: task})
|
||||
// Execute assignments in parallel
|
||||
board := &SharedBoard{}
|
||||
results := r.runMembersParallel(ctx, assignments, board, skillXML)
|
||||
|
||||
memberSystem := member.BuildSystemPrompt(skillXML)
|
||||
memberMsgs := []llm.Message{
|
||||
llm.NewMsg("system", memberSystem),
|
||||
llm.NewMsg("user", task),
|
||||
}
|
||||
var memberReply strings.Builder
|
||||
_, err := member.Chat(ctx, memberMsgs, func(token string) {
|
||||
memberReply.WriteString(token)
|
||||
r.emit(Event{Type: EvtAgentMessage, Agent: memberName, Role: "member", Content: token, Streaming: true})
|
||||
})
|
||||
if err != nil {
|
||||
results.WriteString(fmt.Sprintf("[%s] error: %v\n", memberName, err))
|
||||
continue
|
||||
}
|
||||
result := memberReply.String()
|
||||
results.WriteString(fmt.Sprintf("[%s] %s\n", memberName, result))
|
||||
r.AppendHistory("member", memberName, result)
|
||||
|
||||
// Save workspace file if member produced a document
|
||||
if strings.Contains(result, "# ") {
|
||||
filename := fmt.Sprintf("%s-%s.md", memberName, time.Now().Format("20060102-150405"))
|
||||
r.saveWorkspace(filename, result)
|
||||
r.emit(Event{Type: EvtWorkspaceFile, Filename: filename, Content: result})
|
||||
}
|
||||
}
|
||||
// Run challenge round
|
||||
r.runChallengeRound(ctx, board, skillXML)
|
||||
|
||||
// Feed results back to master for review
|
||||
r.setStatus(StatusThinking, "", "")
|
||||
masterMsgs = append(masterMsgs, llm.NewMsg("user", "Team results:\n"+results.String()+"\nPlease review. If satisfied output DONE:<summary>, otherwise output ASSIGN instructions for revisions."))
|
||||
var resultsStr strings.Builder
|
||||
for memberName, result := range results {
|
||||
resultsStr.WriteString(fmt.Sprintf("[%s] %s\n", memberName, result))
|
||||
}
|
||||
boardCtx := board.ToContext()
|
||||
feedbackMsg := "Team results:\n" + resultsStr.String()
|
||||
if boardCtx != "" {
|
||||
feedbackMsg += "\n\nTeam board:\n" + boardCtx
|
||||
}
|
||||
feedbackMsg += "\n\nPlease review. If satisfied output DONE:<summary>, otherwise output ASSIGN instructions for revisions."
|
||||
masterMsgs = append(masterMsgs, llm.NewMsg("user", feedbackMsg))
|
||||
|
||||
// Update tasks
|
||||
r.updateTasks(masterMsgs)
|
||||
|
||||
@ -140,9 +140,9 @@ export const useStore = create<AppState>((set, get) => {
|
||||
if (ev.type === 'agent_message') {
|
||||
set(s => {
|
||||
const msgs = [...(s.messages[roomId] || [])]
|
||||
const last = msgs[msgs.length - 1]
|
||||
if (last?.streaming && last.agent === ev.agent) {
|
||||
msgs[msgs.length - 1] = { ...last, content: last.content + ev.content, streaming: ev.streaming }
|
||||
const idx = msgs.findLastIndex(m => m.streaming && m.agent === ev.agent)
|
||||
if (idx !== -1) {
|
||||
msgs[idx] = { ...msgs[idx], content: msgs[idx].content + ev.content, streaming: ev.streaming }
|
||||
} else {
|
||||
msgs.push({ id: Date.now().toString(), agent: ev.agent, role: ev.role, content: ev.content, streaming: ev.streaming })
|
||||
}
|
||||
|
||||
@ -17,7 +17,7 @@ export interface Room {
|
||||
export interface Message {
|
||||
id: string
|
||||
agent: string
|
||||
role: 'user' | 'master' | 'member'
|
||||
role: 'user' | 'master' | 'member' | 'challenge'
|
||||
content: string
|
||||
streaming?: boolean
|
||||
}
|
||||
@ -33,7 +33,7 @@ export interface SkillMeta {
|
||||
}
|
||||
|
||||
export type WsEvent =
|
||||
| { type: 'agent_message'; agent: string; role: 'master' | 'member'; content: string; streaming: boolean }
|
||||
| { type: 'agent_message'; agent: string; role: 'master' | 'member' | 'challenge'; content: string; streaming: boolean }
|
||||
| { type: 'room_status'; status: RoomStatus; active_agent?: string; action?: string }
|
||||
| { type: 'task_assign'; from: string; to: string; task: string }
|
||||
| { type: 'tasks_update'; content: string }
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user