feat(squad): define workspace directory metadata models and engine
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
committed by
weichen
co-authored by
Cursor
parent
29e00db0d9
commit
6bbbb16a1c
@@ -0,0 +1,160 @@
|
||||
package squad
|
||||
|
||||
import "time"
|
||||
|
||||
// WorkspaceMetadata 记录工作区全局元数据
|
||||
type WorkspaceMetadata struct {
|
||||
ID string `json:"id"`
|
||||
SessionID int64 `json:"session_id"`
|
||||
ProjectID int64 `json:"project_id"`
|
||||
TaskKey string `json:"task_key"`
|
||||
StageTitle string `json:"stage_title"`
|
||||
ProjectName string `json:"project_name"`
|
||||
CustomerName string `json:"customer_name"`
|
||||
DeviceType string `json:"device_type,omitempty"`
|
||||
IntendedUse string `json:"intended_use,omitempty"`
|
||||
TargetMarket string `json:"target_market,omitempty"`
|
||||
Status string `json:"status"` // created, running, completed, failed
|
||||
Backend string `json:"backend"`
|
||||
WorkDir string `json:"work_dir"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
CompletedAt *time.Time `json:"completed_at,omitempty"`
|
||||
Metadata map[string]string `json:"metadata,omitempty"`
|
||||
}
|
||||
|
||||
// AgentSpec 声明单个专员角色在小队中的设定
|
||||
type AgentSpec struct {
|
||||
Role string `json:"role"`
|
||||
Name string `json:"name"`
|
||||
Model string `json:"model"`
|
||||
Tools []string `json:"tools"`
|
||||
BashPermission string `json:"bash_permission"` // allow, deny
|
||||
Responsibilities []string `json:"responsibilities"`
|
||||
ForbiddenActions []string `json:"forbidden_actions"`
|
||||
InstructionsFile string `json:"instructions_file,omitempty"`
|
||||
}
|
||||
|
||||
// SquadConfig 激活的智能体小队编制
|
||||
type SquadConfig struct {
|
||||
SquadName string `json:"squad_name"`
|
||||
Version string `json:"version"`
|
||||
Agents []AgentSpec `json:"agents"`
|
||||
}
|
||||
|
||||
// StepSpec 单个任务步骤规格定义
|
||||
type StepSpec struct {
|
||||
ID string `json:"id"`
|
||||
Role string `json:"role"`
|
||||
Title string `json:"title"`
|
||||
SubWorkspace string `json:"sub_workspace"` // 相对子路径,如 tasks/01_organize
|
||||
Mode string `json:"mode"` // execute, plan, review_plan, replan
|
||||
Parallel bool `json:"parallel"`
|
||||
Needs []string `json:"needs,omitempty"`
|
||||
Accept []string `json:"accept,omitempty"`
|
||||
Status string `json:"status"` // pending, running, completed, failed, skipped
|
||||
DurationMs int64 `json:"duration_ms,omitempty"`
|
||||
Error string `json:"error,omitempty"`
|
||||
}
|
||||
|
||||
// WorkflowPlan 动态任务编排清单
|
||||
type WorkflowPlan struct {
|
||||
WorkflowID string `json:"workflow_id"`
|
||||
Status string `json:"status"` // pending, in_progress, completed, failed
|
||||
CurrentStepIndex int `json:"current_step_index"`
|
||||
Steps []StepSpec `json:"steps"`
|
||||
}
|
||||
|
||||
// ContextItem 上下文项目元数据
|
||||
type ContextItem struct {
|
||||
File string `json:"file"` // 相对路径,如 context/user.md
|
||||
Description string `json:"description"` // 用途说明
|
||||
AuthorityLevel string `json:"authority_level"` // authoritative, primary_evidence, reference_standard, supplementary, inferred
|
||||
Guidance string `json:"guidance"` // 使用建议与优先级说明
|
||||
}
|
||||
|
||||
// ContextIndex 上下文索引表
|
||||
type ContextIndex struct {
|
||||
Items []ContextItem `json:"context_items"`
|
||||
}
|
||||
|
||||
// MaterialItem 输入材料
|
||||
type MaterialItem struct {
|
||||
Filename string `json:"filename"`
|
||||
Content []byte `json:"content"`
|
||||
}
|
||||
|
||||
// KnowledgeHit 法规知识库检索命中条目
|
||||
type KnowledgeHit struct {
|
||||
DocCode string `json:"doc_code"`
|
||||
Category string `json:"category,omitempty"` // standards, guidance, laws, catalog
|
||||
Title string `json:"title"`
|
||||
Content string `json:"content"`
|
||||
Source string `json:"source"`
|
||||
}
|
||||
|
||||
// PriorDocItem 前序阶段归档文档
|
||||
type PriorDocItem struct {
|
||||
DocKey string `json:"doc_key"`
|
||||
Title string `json:"title"`
|
||||
Filename string `json:"filename"`
|
||||
Content string `json:"content"`
|
||||
Stage string `json:"stage"`
|
||||
}
|
||||
|
||||
// FactItem 黑板事实项
|
||||
type FactItem struct {
|
||||
ID string `json:"id"`
|
||||
Claim string `json:"claim"`
|
||||
Source string `json:"source"`
|
||||
Verified bool `json:"verified"`
|
||||
}
|
||||
|
||||
// GapItem 黑板缺口项
|
||||
type GapItem struct {
|
||||
ID string `json:"id"`
|
||||
Item string `json:"item"`
|
||||
Owner string `json:"owner"` // user, agent
|
||||
Blocking bool `json:"blocking"` // 是否阻塞后续推进
|
||||
Resolved bool `json:"resolved"`
|
||||
}
|
||||
|
||||
// ImplicationItem 黑板含义项
|
||||
type ImplicationItem struct {
|
||||
ID string `json:"id"`
|
||||
Topic string `json:"topic"`
|
||||
Statement string `json:"statement"`
|
||||
}
|
||||
|
||||
// BlackboardState 结构化共享黑板
|
||||
type BlackboardState struct {
|
||||
Facts []FactItem `json:"facts"`
|
||||
Gaps []GapItem `json:"gaps"`
|
||||
Implications []ImplicationItem `json:"implications"`
|
||||
Fields map[string]string `json:"fields"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
}
|
||||
|
||||
// QualityReport 自动化质量门禁报告
|
||||
type QualityReport struct {
|
||||
Passed bool `json:"passed"`
|
||||
Score float64 `json:"score"`
|
||||
TitleCheck bool `json:"title_check"`
|
||||
MissingSections []string `json:"missing_sections,omitempty"`
|
||||
ProhibitedPhrases []string `json:"prohibited_phrases,omitempty"`
|
||||
QuoteMatches bool `json:"quote_matches"`
|
||||
Violations []string `json:"violations,omitempty"`
|
||||
CheckedAt time.Time `json:"checked_at"`
|
||||
}
|
||||
|
||||
// Manifest 执行清单与防篡改审计
|
||||
type Manifest struct {
|
||||
ManifestVersion string `json:"manifest_version"`
|
||||
WorkspaceID string `json:"workspace_id"`
|
||||
DocKey string `json:"doc_key"`
|
||||
GeneratedAt time.Time `json:"generated_at"`
|
||||
Deliverable string `json:"deliverable"`
|
||||
Files map[string]string `json:"files"` // relPath -> sha256
|
||||
QualityReport QualityReport `json:"quality_report"`
|
||||
DurationMs int64 `json:"duration_ms"`
|
||||
}
|
||||
@@ -0,0 +1,741 @@
|
||||
package squad
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"chat-agent-system/internal/template"
|
||||
)
|
||||
|
||||
// WorkspacePrepareRequest 工作区准备请求
|
||||
type WorkspacePrepareRequest struct {
|
||||
WorkDir string
|
||||
SessionID int64
|
||||
ProjectID int64
|
||||
TaskKey string
|
||||
StageTitle string
|
||||
ProjectName string
|
||||
CustomerName string
|
||||
DeviceType string
|
||||
IntendedUse string
|
||||
TargetMarket string
|
||||
Backend string
|
||||
TemplateContent string
|
||||
Materials []MaterialItem
|
||||
KnowledgeHits []KnowledgeHit
|
||||
PriorDocs []PriorDocItem
|
||||
UserContext string
|
||||
ProjectContext string
|
||||
AgentContext string
|
||||
MemoryBankFiles map[string]string
|
||||
CustomAgents []AgentSpec
|
||||
WorkflowSteps []StepSpec
|
||||
InitialFields map[string]string
|
||||
Metadata map[string]string
|
||||
}
|
||||
|
||||
// Workspace 表示已初始化的工作区句柄
|
||||
type Workspace struct {
|
||||
WorkDir string
|
||||
Meta WorkspaceMetadata
|
||||
Squad SquadConfig
|
||||
Workflow WorkflowPlan
|
||||
}
|
||||
|
||||
// WorkspaceEngine 工作区生命周期与沙箱构建引擎
|
||||
type WorkspaceEngine struct{}
|
||||
|
||||
func NewWorkspaceEngine() *WorkspaceEngine {
|
||||
return &WorkspaceEngine{}
|
||||
}
|
||||
|
||||
// PrepareWorkspace 初始化自包含工作区目录
|
||||
func (e *WorkspaceEngine) PrepareWorkspace(req WorkspacePrepareRequest) (*Workspace, error) {
|
||||
workDir := req.WorkDir
|
||||
if workDir == "" {
|
||||
ts := time.Now().UnixNano()
|
||||
workDir = filepath.Join("data", "dev", "workspaces", "bizdev", fmt.Sprintf("task_%s_%d", req.TaskKey, ts))
|
||||
}
|
||||
if err := os.MkdirAll(workDir, 0o755); err != nil {
|
||||
return nil, fmt.Errorf("create workspace root dir: %w", err)
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
wsID := fmt.Sprintf("ws_%s_%d", req.TaskKey, now.UnixNano())
|
||||
if req.Metadata != nil && req.Metadata["id"] != "" {
|
||||
wsID = req.Metadata["id"]
|
||||
}
|
||||
|
||||
// 1. 初始化 workspace.json
|
||||
meta := WorkspaceMetadata{
|
||||
ID: wsID,
|
||||
SessionID: req.SessionID,
|
||||
ProjectID: req.ProjectID,
|
||||
TaskKey: req.TaskKey,
|
||||
StageTitle: req.StageTitle,
|
||||
ProjectName: req.ProjectName,
|
||||
CustomerName: req.CustomerName,
|
||||
DeviceType: req.DeviceType,
|
||||
IntendedUse: req.IntendedUse,
|
||||
TargetMarket: req.TargetMarket,
|
||||
Status: "created",
|
||||
Backend: req.Backend,
|
||||
WorkDir: workDir,
|
||||
CreatedAt: now,
|
||||
UpdatedAt: now,
|
||||
Metadata: req.Metadata,
|
||||
}
|
||||
if err := writeJSON(filepath.Join(workDir, "workspace.json"), meta); err != nil {
|
||||
return nil, fmt.Errorf("write workspace.json: %w", err)
|
||||
}
|
||||
|
||||
// 2. 初始化 squad.json
|
||||
agents := req.CustomAgents
|
||||
if len(agents) == 0 {
|
||||
agents = DefaultSquadAgents(req.TaskKey)
|
||||
}
|
||||
squadCfg := SquadConfig{
|
||||
SquadName: fmt.Sprintf("%s_squad", req.TaskKey),
|
||||
Version: "1.0.0",
|
||||
Agents: agents,
|
||||
}
|
||||
if err := writeJSON(filepath.Join(workDir, "squad.json"), squadCfg); err != nil {
|
||||
return nil, fmt.Errorf("write squad.json: %w", err)
|
||||
}
|
||||
|
||||
// 3. 初始化 workflow.json
|
||||
steps := req.WorkflowSteps
|
||||
if len(steps) == 0 {
|
||||
steps = DefaultWorkflowSteps(req.TaskKey)
|
||||
}
|
||||
wf := WorkflowPlan{
|
||||
WorkflowID: fmt.Sprintf("wf_%s_%d", req.TaskKey, now.UnixNano()),
|
||||
Status: "pending",
|
||||
CurrentStepIndex: 0,
|
||||
Steps: steps,
|
||||
}
|
||||
if err := writeJSON(filepath.Join(workDir, "workflow.json"), wf); err != nil {
|
||||
return nil, fmt.Errorf("write workflow.json: %w", err)
|
||||
}
|
||||
|
||||
// 4. 写入自描述模板 template.md
|
||||
tmplContent := req.TemplateContent
|
||||
if strings.TrimSpace(tmplContent) == "" {
|
||||
var err error
|
||||
tmplContent, err = template.GetDocTemplate(req.TaskKey)
|
||||
if err != nil {
|
||||
tmplContent = fmt.Sprintf("# %s\n\n## 1. 结论\n\n待确认\n\n## 2. 正文\n\n待确认\n", req.StageTitle)
|
||||
}
|
||||
}
|
||||
vars := map[string]string{
|
||||
"project_name": req.ProjectName,
|
||||
"product_name": req.ProjectName,
|
||||
"customer_name": req.CustomerName,
|
||||
"device_type": req.DeviceType,
|
||||
"intended_use": req.IntendedUse,
|
||||
"target_market": req.TargetMarket,
|
||||
"generated_at": now.Format("2006-01-02 15:04:05"),
|
||||
"snapshot_id": wsID,
|
||||
}
|
||||
for k, v := range req.InitialFields {
|
||||
vars[k] = v
|
||||
}
|
||||
renderedTmpl := template.RenderDoc(tmplContent, vars)
|
||||
if err := os.WriteFile(filepath.Join(workDir, "template.md"), []byte(renderedTmpl), 0o644); err != nil {
|
||||
return nil, fmt.Errorf("write template.md: %w", err)
|
||||
}
|
||||
|
||||
// 5. 初始化 blackboard.json
|
||||
bb := BlackboardState{
|
||||
Facts: make([]FactItem, 0),
|
||||
Gaps: make([]GapItem, 0),
|
||||
Implications: make([]ImplicationItem, 0),
|
||||
Fields: make(map[string]string),
|
||||
UpdatedAt: now,
|
||||
}
|
||||
if req.CustomerName != "" {
|
||||
bb.Fields["customer_name"] = req.CustomerName
|
||||
bb.Facts = append(bb.Facts, FactItem{
|
||||
ID: "fact_01",
|
||||
Claim: "客户名称为 " + req.CustomerName,
|
||||
Source: "用户输入",
|
||||
Verified: true,
|
||||
})
|
||||
}
|
||||
if req.ProjectName != "" {
|
||||
bb.Fields["project_name"] = req.ProjectName
|
||||
}
|
||||
if req.DeviceType != "" {
|
||||
bb.Fields["device_type"] = req.DeviceType
|
||||
}
|
||||
if req.IntendedUse != "" {
|
||||
bb.Fields["intended_use"] = req.IntendedUse
|
||||
}
|
||||
if req.TargetMarket != "" {
|
||||
bb.Fields["target_market"] = req.TargetMarket
|
||||
}
|
||||
for k, v := range req.InitialFields {
|
||||
bb.Fields[k] = v
|
||||
}
|
||||
if err := writeJSON(filepath.Join(workDir, "blackboard.json"), bb); err != nil {
|
||||
return nil, fmt.Errorf("write blackboard.json: %w", err)
|
||||
}
|
||||
|
||||
// 6. 构造自描述上下文体系 context/
|
||||
if err := e.buildContextSystem(workDir, req); err != nil {
|
||||
return nil, fmt.Errorf("build context system: %w", err)
|
||||
}
|
||||
|
||||
// 7. 创建子工作区 tasks/<sub_dir> 并注入 TASK.md 与 AGENTS.md
|
||||
if err := e.buildSubWorkspaces(workDir, wf, squadCfg, renderedTmpl); err != nil {
|
||||
return nil, fmt.Errorf("build sub workspaces: %w", err)
|
||||
}
|
||||
|
||||
// 8. 创建 artifacts/ 与基础日志
|
||||
_ = os.MkdirAll(filepath.Join(workDir, "artifacts"), 0o755)
|
||||
logEntry := fmt.Sprintf("[%s] 工作区创建完成: ID=%s, Task=%s\n", now.Format(time.RFC3339), wsID, req.TaskKey)
|
||||
_ = os.WriteFile(filepath.Join(workDir, "execution.log"), []byte(logEntry), 0o644)
|
||||
|
||||
return &Workspace{
|
||||
WorkDir: workDir,
|
||||
Meta: meta,
|
||||
Squad: squadCfg,
|
||||
Workflow: wf,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// buildContextSystem 构建具有权威度说明的 context/ 体系
|
||||
func (e *WorkspaceEngine) buildContextSystem(workDir string, req WorkspacePrepareRequest) error {
|
||||
ctxDir := filepath.Join(workDir, "context")
|
||||
_ = os.MkdirAll(filepath.Join(ctxDir, "materials_text"), 0o755)
|
||||
_ = os.MkdirAll(filepath.Join(ctxDir, "knowledge"), 0o755)
|
||||
_ = os.MkdirAll(filepath.Join(ctxDir, "prior_docs"), 0o755)
|
||||
_ = os.MkdirAll(filepath.Join(ctxDir, "memory"), 0o755)
|
||||
|
||||
var contextItems []ContextItem
|
||||
|
||||
// 1. user.md
|
||||
userContent := req.UserContext
|
||||
if strings.TrimSpace(userContent) == "" {
|
||||
userContent = fmt.Sprintf("# 用户角色与偏好\n\n- 用户角色: 商务与技术负责人\n- 决策风格: 严谨可追溯,注重四段式论据\n- 授权边界: 预算与商业承诺必须经由用户拍板\n")
|
||||
}
|
||||
_ = os.WriteFile(filepath.Join(ctxDir, "user.md"), []byte(userContent), 0o644)
|
||||
contextItems = append(contextItems, ContextItem{
|
||||
File: "context/user.md",
|
||||
Description: "操作用户的业务角色、决策授权边界与协同偏好",
|
||||
AuthorityLevel: "authoritative",
|
||||
Guidance: "必须尊重用户设定的目标市场与边界,用户未决事项一律列入待确认清单,严禁越权替用户拍板",
|
||||
})
|
||||
|
||||
// 2. project.md
|
||||
projContent := req.ProjectContext
|
||||
if strings.TrimSpace(projContent) == "" {
|
||||
projContent = fmt.Sprintf("# 项目全局基线\n\n- 项目名称: %s\n- 客户名称: %s\n- 产品类型: %s\n- 预期用途: %s\n- 目标市场: %s\n",
|
||||
req.ProjectName, req.CustomerName, req.DeviceType, req.IntendedUse, req.TargetMarket)
|
||||
}
|
||||
_ = os.WriteFile(filepath.Join(ctxDir, "project.md"), []byte(projContent), 0o644)
|
||||
contextItems = append(contextItems, ContextItem{
|
||||
File: "context/project.md",
|
||||
Description: "项目立项章程与全局事实基线",
|
||||
AuthorityLevel: "authoritative",
|
||||
Guidance: "本项目核心前提。所有后续分析论证不得推翻已锁定的项目名称与核心预期用途",
|
||||
})
|
||||
|
||||
// 3. agent.md
|
||||
agentContent := req.AgentContext
|
||||
if strings.TrimSpace(agentContent) == "" {
|
||||
agentContent = "# 智能体小队协同通用规范\n\n- 事实闭环: 严禁凭空捏造未提供之参数或法规\n- 引用规范: 引用必须标注出处〔材料:...〕或〔法规:...〕\n- 负向红线: 禁止敷衍写作、禁止'不做展开'、'从略'\n"
|
||||
}
|
||||
_ = os.WriteFile(filepath.Join(ctxDir, "agent.md"), []byte(agentContent), 0o644)
|
||||
contextItems = append(contextItems, ContextItem{
|
||||
File: "context/agent.md",
|
||||
Description: "智能体通用协同纪律与引用要求",
|
||||
AuthorityLevel: "authoritative",
|
||||
Guidance: "所有小队角色必须严格执行的专业守则与写作禁令",
|
||||
})
|
||||
|
||||
// 4. materials_text/
|
||||
if len(req.Materials) > 0 {
|
||||
var allBuilder strings.Builder
|
||||
for _, m := range req.Materials {
|
||||
name := filepath.Base(m.Filename)
|
||||
if name == "" || name == "." {
|
||||
name = "material.txt"
|
||||
}
|
||||
p := filepath.Join(ctxDir, "materials_text", name)
|
||||
_ = os.WriteFile(p, m.Content, 0o644)
|
||||
|
||||
// Also write to root materials_text/ for backward compatibility
|
||||
rootMatDir := filepath.Join(workDir, "materials_text")
|
||||
_ = os.MkdirAll(rootMatDir, 0o755)
|
||||
_ = os.WriteFile(filepath.Join(rootMatDir, name), m.Content, 0o644)
|
||||
|
||||
allBuilder.WriteString(fmt.Sprintf("\n\n=== 文件: %s ===\n\n", name))
|
||||
allBuilder.Write(m.Content)
|
||||
|
||||
contextItems = append(contextItems, ContextItem{
|
||||
File: filepath.ToSlash(filepath.Join("context", "materials_text", name)),
|
||||
Description: fmt.Sprintf("用户上传的输入材料文本: %s", name),
|
||||
AuthorityLevel: "primary_evidence",
|
||||
Guidance: "核心原始证据。提取参数需标明出处;材料内部矛盾需在矛盾表中并列呈现",
|
||||
})
|
||||
}
|
||||
_ = os.WriteFile(filepath.Join(ctxDir, "materials_text", "_all.txt"), []byte(allBuilder.String()), 0o644)
|
||||
}
|
||||
|
||||
// 5. knowledge/
|
||||
if len(req.KnowledgeHits) > 0 {
|
||||
var kbBuilder strings.Builder
|
||||
kbBuilder.WriteString("# 法规与标准检索条文摘录\n\n")
|
||||
for _, hit := range req.KnowledgeHits {
|
||||
kbBuilder.WriteString(fmt.Sprintf("### [%s] %s %s\n\n%s\n\n> 来源: %s\n\n---\n\n",
|
||||
strings.ToUpper(hit.Category), hit.DocCode, hit.Title, hit.Content, hit.Source))
|
||||
}
|
||||
_ = os.WriteFile(filepath.Join(ctxDir, "knowledge", "hits.md"), []byte(kbBuilder.String()), 0o644)
|
||||
contextItems = append(contextItems, ContextItem{
|
||||
File: "context/knowledge/hits.md",
|
||||
Description: "法规知识库中命中的医疗器械分类、国家标准与指导原则",
|
||||
AuthorityLevel: "reference_standard",
|
||||
Guidance: "合规与技术论据源。引用必须带标准号;涉及审批结论需标注'需向药监局审评中心确认'",
|
||||
})
|
||||
}
|
||||
|
||||
// 6. prior_docs/
|
||||
if len(req.PriorDocs) > 0 {
|
||||
var indexBuilder strings.Builder
|
||||
indexBuilder.WriteString("# 前序阶段交付物索引\n\n")
|
||||
for _, doc := range req.PriorDocs {
|
||||
filename := doc.Filename
|
||||
if filename == "" {
|
||||
filename = doc.DocKey + ".md"
|
||||
}
|
||||
p := filepath.Join(ctxDir, "prior_docs", filename)
|
||||
_ = os.WriteFile(p, []byte(doc.Content), 0o644)
|
||||
indexBuilder.WriteString(fmt.Sprintf("- [%s] `%s`: %s (阶段: %s)\n", doc.DocKey, filename, doc.Title, doc.Stage))
|
||||
|
||||
contextItems = append(contextItems, ContextItem{
|
||||
File: filepath.ToSlash(filepath.Join("context", "prior_docs", filename)),
|
||||
Description: fmt.Sprintf("前序已归档文档: %s (%s)", doc.Title, doc.DocKey),
|
||||
AuthorityLevel: "primary_evidence",
|
||||
Guidance: "前序阶段已批准的结论基准。本阶段内容应与前序结论保持自洽",
|
||||
})
|
||||
}
|
||||
_ = os.WriteFile(filepath.Join(ctxDir, "prior_docs", "index.md"), []byte(indexBuilder.String()), 0o644)
|
||||
}
|
||||
|
||||
// 7. memory/
|
||||
if len(req.MemoryBankFiles) > 0 {
|
||||
for k, v := range req.MemoryBankFiles {
|
||||
p := filepath.Join(ctxDir, "memory", k)
|
||||
_ = os.WriteFile(p, []byte(v), 0o644)
|
||||
}
|
||||
} else {
|
||||
// 默认四大核心资产骨架
|
||||
_ = os.WriteFile(filepath.Join(ctxDir, "memory", "projectbrief.md"), []byte(fmt.Sprintf("# 项目章程\n- 项目: %s\n- 客户: %s\n", req.ProjectName, req.CustomerName)), 0o644)
|
||||
_ = os.WriteFile(filepath.Join(ctxDir, "memory", "activeContext.md"), []byte(fmt.Sprintf("# 当前焦点\n- 阶段: %s\n- 时间: %s\n", req.TaskKey, time.Now().Format("2006-01-02 15:04"))), 0o644)
|
||||
_ = os.WriteFile(filepath.Join(ctxDir, "memory", "systemPatterns.md"), []byte("# 规范与模式\n- 遵循四段式结构与严格角色边界\n- 单一真实源原则\n"), 0o644)
|
||||
_ = os.WriteFile(filepath.Join(ctxDir, "memory", "progress.md"), []byte(fmt.Sprintf("# 进度\n- 当前正在进行: %s\n", req.StageTitle)), 0o644)
|
||||
}
|
||||
contextItems = append(contextItems, ContextItem{
|
||||
File: "context/memory/",
|
||||
Description: "长期项目记忆银行 (ActiveContext, Progress, ProjectBrief, SystemPatterns)",
|
||||
AuthorityLevel: "supplementary",
|
||||
Guidance: "协同上下文背景参考,防止上下文遗忘",
|
||||
})
|
||||
|
||||
// 8. 写入 context/index.json
|
||||
ctxIndex := ContextIndex{Items: contextItems}
|
||||
if err := writeJSON(filepath.Join(ctxDir, "index.json"), ctxIndex); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 9. 写入 context/README.md 指引
|
||||
readmeContent := `# 上下文使用指引 (Context Reading Guide)
|
||||
|
||||
本目录为当前智能体任务的自包含上下文沙箱。所有智能体专员在执行任务时,必须按照以下权威度等级(Authority Level)采纳信息:
|
||||
|
||||
| 权威度级别 | 适用文件类别 | 采信与处理规则 |
|
||||
|---|---|---|
|
||||
| **authoritative** | ` + "`user.md`" + `, ` + "`project.md`" + `, ` + "`agent.md`" + ` | **最高权威**。必须绝对遵循;严禁越权替用户拍板,未决项列入待确认清单 |
|
||||
| **primary_evidence** | ` + "`materials_text/*`" + `, ` + "`prior_docs/*`" + ` | **原始事实证据**。引用必须标明出处条文;若材料间存在矛盾,并列呈现 |
|
||||
| **reference_standard** | ` + "`knowledge/hits.md`" + ` | **标准与法规依据**。必须指明法规/标准代码;涉及监管裁定标注需窗口核实 |
|
||||
| **supplementary** | ` + "`memory/*`" + ` | **补充记忆背景**。仅供背景了解,不作为唯一支撑证据 |
|
||||
|
||||
---
|
||||
`
|
||||
return os.WriteFile(filepath.Join(ctxDir, "README.md"), []byte(readmeContent), 0o644)
|
||||
}
|
||||
|
||||
// buildSubWorkspaces 为 workflow 中的各个子任务构建独立沙箱
|
||||
func (e *WorkspaceEngine) buildSubWorkspaces(workDir string, wf WorkflowPlan, squadCfg SquadConfig, renderedTmpl string) error {
|
||||
agentMap := make(map[string]AgentSpec)
|
||||
for _, a := range squadCfg.Agents {
|
||||
agentMap[a.Role] = a
|
||||
}
|
||||
|
||||
for _, step := range wf.Steps {
|
||||
subDir := filepath.Join(workDir, filepath.FromSlash(step.SubWorkspace))
|
||||
if err := os.MkdirAll(subDir, 0o755); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 1. 生成 AGENTS.md
|
||||
var agentsBuilder strings.Builder
|
||||
inst, err := template.GetAgentInstruction(step.Role)
|
||||
if err == nil {
|
||||
agentsBuilder.WriteString(inst)
|
||||
} else {
|
||||
spec := agentMap[step.Role]
|
||||
agentsBuilder.WriteString(fmt.Sprintf("# 角色: %s (%s)\n\n", spec.Name, spec.Role))
|
||||
if len(spec.Responsibilities) > 0 {
|
||||
agentsBuilder.WriteString("## 核心职责\n")
|
||||
for _, r := range spec.Responsibilities {
|
||||
agentsBuilder.WriteString(fmt.Sprintf("- %s\n", r))
|
||||
}
|
||||
agentsBuilder.WriteString("\n")
|
||||
}
|
||||
if len(spec.ForbiddenActions) > 0 {
|
||||
agentsBuilder.WriteString("## 严禁行为(红线)\n")
|
||||
for _, f := range spec.ForbiddenActions {
|
||||
agentsBuilder.WriteString(fmt.Sprintf("- ❌ %s\n", f))
|
||||
}
|
||||
agentsBuilder.WriteString("\n")
|
||||
}
|
||||
}
|
||||
_ = os.WriteFile(filepath.Join(subDir, "AGENTS.md"), []byte(agentsBuilder.String()), 0o644)
|
||||
_ = os.WriteFile(filepath.Join(subDir, "INSTRUCTIONS.md"), []byte(agentsBuilder.String()), 0o644)
|
||||
|
||||
// 2. 生成 TASK.md
|
||||
deliverableName := "output.md"
|
||||
if step.Mode == "plan" || step.Mode == "replan" {
|
||||
deliverableName = "output_plan.md"
|
||||
} else if step.Mode == "review_plan" {
|
||||
deliverableName = "review_notes.md"
|
||||
}
|
||||
|
||||
var taskBuilder strings.Builder
|
||||
taskBuilder.WriteString(fmt.Sprintf("# 任务执行指令: %s\n\n", step.Title))
|
||||
taskBuilder.WriteString(fmt.Sprintf("- **步骤 ID**: `%s`\n", step.ID))
|
||||
taskBuilder.WriteString(fmt.Sprintf("- **分配角色**: `%s`\n", step.Role))
|
||||
taskBuilder.WriteString(fmt.Sprintf("- **执行模式**: `%s`\n", step.Mode))
|
||||
taskBuilder.WriteString(fmt.Sprintf("- **交付产物**: `%s`\n\n", deliverableName))
|
||||
|
||||
taskBuilder.WriteString("## 环境与输入导航\n")
|
||||
taskBuilder.WriteString("请仔细查阅上级目录资源:\n")
|
||||
taskBuilder.WriteString("- `../../context/index.json` 及 `../../context/README.md`:阅读上下文指引\n")
|
||||
taskBuilder.WriteString("- `../../template.md`:自描述交付模板(嵌入四段式与格式约束)\n")
|
||||
taskBuilder.WriteString("- `../../blackboard.json`:前序专员抽取的结构化事实与缺口\n")
|
||||
taskBuilder.WriteString("- `../../context/materials_text/`:原始材料全文\n")
|
||||
taskBuilder.WriteString("- `../../context/knowledge/`:法规与标准命中快照\n\n")
|
||||
|
||||
if len(step.Needs) > 0 {
|
||||
taskBuilder.WriteString("## 前序依赖产出\n")
|
||||
for _, n := range step.Needs {
|
||||
taskBuilder.WriteString(fmt.Sprintf("- `../../%s`\n", filepath.ToSlash(n)))
|
||||
}
|
||||
taskBuilder.WriteString("\n")
|
||||
}
|
||||
|
||||
if len(step.Accept) > 0 {
|
||||
taskBuilder.WriteString("## 验收标准(Checklist)\n")
|
||||
for _, a := range step.Accept {
|
||||
taskBuilder.WriteString(fmt.Sprintf("- [ ] %s\n", a))
|
||||
}
|
||||
taskBuilder.WriteString("\n")
|
||||
}
|
||||
|
||||
taskBuilder.WriteString("## 交付契约与写作纪律\n")
|
||||
taskBuilder.WriteString(fmt.Sprintf("请在当前目录下生成完整交付物 `%s`:\n", deliverableName))
|
||||
taskBuilder.WriteString("1. 严格四段式展开(结论句 → 表格/列表 → 解读 → 支撑出处)。\n")
|
||||
taskBuilder.WriteString("2. 禁止使用'不做展开'、'从略'、'篇幅有限'等省略敷衍套话。\n")
|
||||
taskBuilder.WriteString("3. 关键事实必须标注出处,未知项列入待确认清单。\n")
|
||||
if step.Role == "organizer" {
|
||||
taskBuilder.WriteString("4. 从材料提取的客观字段,请在当前工作目录下输出 `fields.json`。\n")
|
||||
}
|
||||
|
||||
_ = os.WriteFile(filepath.Join(subDir, "TASK.md"), []byte(taskBuilder.String()), 0o644)
|
||||
|
||||
// 3. 写入 input_refs.json
|
||||
inputRefs := map[string]any{
|
||||
"step_id": step.ID,
|
||||
"role": step.Role,
|
||||
"needs": step.Needs,
|
||||
"target_file": deliverableName,
|
||||
}
|
||||
_ = writeJSON(filepath.Join(subDir, "input_refs.json"), inputRefs)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// DefaultSquadAgents 根据任务类型返回默认小队成员编制
|
||||
func DefaultSquadAgents(taskKey string) []AgentSpec {
|
||||
switch taskKey {
|
||||
case "intake":
|
||||
return []AgentSpec{
|
||||
{
|
||||
Role: "organizer",
|
||||
Name: "材料整理专员",
|
||||
Model: "claude-3-7-sonnet",
|
||||
Tools: []string{"read", "write", "edit"},
|
||||
BashPermission: "deny",
|
||||
Responsibilities: []string{"材料事实抽取", "缺口识别", "字段结构化回填"},
|
||||
ForbiddenActions: []string{"严禁测算价格", "严禁出具法规终结裁定", "严禁撰写正文终稿"},
|
||||
},
|
||||
{
|
||||
Role: "writer",
|
||||
Name: "文档撰写专员",
|
||||
Model: "claude-3-7-sonnet",
|
||||
Tools: []string{"read", "write", "edit"},
|
||||
BashPermission: "deny",
|
||||
Responsibilities: []string{"严格四段式成文", "依据材料出处引用", "模板各章节写满"},
|
||||
ForbiddenActions: []string{"严禁写不做展开或从略", "严禁随意篡改前序事实"},
|
||||
},
|
||||
{
|
||||
Role: "leader",
|
||||
Name: "商务对接负责人",
|
||||
Model: "claude-3-7-sonnet",
|
||||
Tools: []string{"read", "write", "edit"},
|
||||
BashPermission: "deny",
|
||||
Responsibilities: []string{"计划审核", "正文终审", "待确认清单汇总", "对外汇报"},
|
||||
ForbiddenActions: []string{"严禁代用户做商业拍板", "严禁直接交付未审核初稿"},
|
||||
},
|
||||
}
|
||||
case "research":
|
||||
return []AgentSpec{
|
||||
{
|
||||
Role: "searcher",
|
||||
Name: "资料检索专员",
|
||||
Model: "claude-3-7-sonnet",
|
||||
Tools: []string{"read", "write", "edit"},
|
||||
BashPermission: "deny",
|
||||
Responsibilities: []string{"公开资料检索", "法规标准核对", "出处与日期标注"},
|
||||
ForbiddenActions: []string{"严禁编造URL与标准号", "未检索到须标明尝试路径"},
|
||||
},
|
||||
{
|
||||
Role: "market",
|
||||
Name: "市场与竞品分析师",
|
||||
Model: "claude-3-7-sonnet",
|
||||
Tools: []string{"read", "write", "edit"},
|
||||
BashPermission: "deny",
|
||||
Responsibilities: []string{"竞品同维度对比", "市场规模测算", "机会与风险分析"},
|
||||
ForbiddenActions: []string{"严禁编造市场份额与价格", "必须采用统一对比维度"},
|
||||
},
|
||||
{
|
||||
Role: "regulatory",
|
||||
Name: "医疗器械法规专员",
|
||||
Model: "claude-3-7-sonnet",
|
||||
Tools: []string{"read", "write", "edit"},
|
||||
BashPermission: "deny",
|
||||
Responsibilities: []string{"器械分类初筛", "注册路径比对", "关键标准提炼"},
|
||||
ForbiddenActions: []string{"严禁代做行政终局裁定", "必须标注需向窗口确认"},
|
||||
},
|
||||
{
|
||||
Role: "writer",
|
||||
Name: "文档撰写专员",
|
||||
Model: "claude-3-7-sonnet",
|
||||
Tools: []string{"read", "write", "edit"},
|
||||
BashPermission: "deny",
|
||||
Responsibilities: []string{"按计划合成调研报告", "全面四段式展开"},
|
||||
ForbiddenActions: []string{"严禁写不做展开或从略"},
|
||||
},
|
||||
{
|
||||
Role: "leader",
|
||||
Name: "商务对接负责人",
|
||||
Model: "claude-3-7-sonnet",
|
||||
Tools: []string{"read", "write", "edit"},
|
||||
BashPermission: "deny",
|
||||
Responsibilities: []string{"审核撰写计划", "终审报告", "质量把关"},
|
||||
ForbiddenActions: []string{"严禁代用户做商业拍板"},
|
||||
},
|
||||
}
|
||||
default:
|
||||
return []AgentSpec{
|
||||
{
|
||||
Role: "organizer",
|
||||
Name: "材料整理专员",
|
||||
Model: "claude-3-7-sonnet",
|
||||
Tools: []string{"read", "write", "edit"},
|
||||
BashPermission: "deny",
|
||||
Responsibilities: []string{"事实整理与基线对齐"},
|
||||
},
|
||||
{
|
||||
Role: "writer",
|
||||
Name: "文档撰写专员",
|
||||
Model: "claude-3-7-sonnet",
|
||||
Tools: []string{"read", "write", "edit"},
|
||||
BashPermission: "deny",
|
||||
Responsibilities: []string{"正文撰写"},
|
||||
},
|
||||
{
|
||||
Role: "leader",
|
||||
Name: "商务对接负责人",
|
||||
Model: "claude-3-7-sonnet",
|
||||
Tools: []string{"read", "write", "edit"},
|
||||
BashPermission: "deny",
|
||||
Responsibilities: []string{"终审与把关"},
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// DefaultWorkflowSteps 根据任务类型返回最佳实践编排步骤
|
||||
func DefaultWorkflowSteps(taskKey string) []StepSpec {
|
||||
switch taskKey {
|
||||
case "intake":
|
||||
return []StepSpec{
|
||||
{
|
||||
ID: "step_01_organize",
|
||||
Role: "organizer",
|
||||
Title: "抽取材料事实并回填字段",
|
||||
SubWorkspace: "tasks/01_organize",
|
||||
Mode: "execute",
|
||||
Accept: []string{
|
||||
"抽取材料中的客户身份与产品定义",
|
||||
"回填 fields.json 结构化数据",
|
||||
"向 blackboard 写入 facts 与 gaps",
|
||||
},
|
||||
},
|
||||
{
|
||||
ID: "step_02_draft",
|
||||
Role: "writer",
|
||||
Title: "撰写用户初始需求收集表初稿",
|
||||
SubWorkspace: "tasks/02_draft",
|
||||
Mode: "execute",
|
||||
Needs: []string{"tasks/01_organize/output.md"},
|
||||
Accept: []string{
|
||||
"按 template.md 各章节严格四段式展开",
|
||||
"正文翔实,禁止写'不做展开'",
|
||||
"文末集中附录字段快照与待确认清单",
|
||||
},
|
||||
},
|
||||
{
|
||||
ID: "step_03_review",
|
||||
Role: "leader",
|
||||
Title: "审核并定稿交付",
|
||||
SubWorkspace: "tasks/03_review",
|
||||
Mode: "execute",
|
||||
Needs: []string{"tasks/02_draft/output.md"},
|
||||
Accept: []string{
|
||||
"核验全文逻辑一致性与出处标注",
|
||||
"汇编待确认清单并给出下一步建议",
|
||||
"交付终稿 output.md",
|
||||
},
|
||||
},
|
||||
}
|
||||
case "research":
|
||||
return []StepSpec{
|
||||
{
|
||||
ID: "step_01_search",
|
||||
Role: "searcher",
|
||||
Title: "公开资料与标准检索",
|
||||
SubWorkspace: "tasks/01_search",
|
||||
Mode: "execute",
|
||||
Parallel: true,
|
||||
Accept: []string{
|
||||
"检索纪要附带明确出处或未检索到说明",
|
||||
"对本项目含义逐条阐述",
|
||||
},
|
||||
},
|
||||
{
|
||||
ID: "step_02_market",
|
||||
Role: "market",
|
||||
Title: "市场格局与竞品同维度对比",
|
||||
SubWorkspace: "tasks/02_market",
|
||||
Mode: "execute",
|
||||
Parallel: true,
|
||||
Accept: []string{
|
||||
"多维度竞品对比表",
|
||||
"机会与风险对称分析",
|
||||
},
|
||||
},
|
||||
{
|
||||
ID: "step_03_regulatory",
|
||||
Role: "regulatory",
|
||||
Title: "医疗器械法规与指导原则初筛",
|
||||
SubWorkspace: "tasks/03_regulatory",
|
||||
Mode: "execute",
|
||||
Parallel: true,
|
||||
Accept: []string{
|
||||
"分类依据条目与建议类别",
|
||||
"明确标注需向审评窗口确认",
|
||||
},
|
||||
},
|
||||
{
|
||||
ID: "step_04_plan",
|
||||
Role: "writer",
|
||||
Title: "撰写调研报告段落计划",
|
||||
SubWorkspace: "tasks/04_plan",
|
||||
Mode: "plan",
|
||||
Needs: []string{
|
||||
"tasks/01_search/output.md",
|
||||
"tasks/02_market/output.md",
|
||||
"tasks/03_regulatory/output.md",
|
||||
},
|
||||
Accept: []string{"各章节论点与引用规划"},
|
||||
},
|
||||
{
|
||||
ID: "step_05_review_plan",
|
||||
Role: "leader",
|
||||
Title: "审核调研报告撰写计划",
|
||||
SubWorkspace: "tasks/05_review_plan",
|
||||
Mode: "review_plan",
|
||||
Needs: []string{"tasks/04_plan/output_plan.md"},
|
||||
Accept: []string{"出具审核意见 review_notes.md"},
|
||||
},
|
||||
{
|
||||
ID: "step_06_draft",
|
||||
Role: "writer",
|
||||
Title: "按计划合成调研报告正文",
|
||||
SubWorkspace: "tasks/06_draft",
|
||||
Mode: "execute",
|
||||
Needs: []string{
|
||||
"tasks/04_plan/output_plan.md",
|
||||
"tasks/05_review_plan/review_notes.md",
|
||||
},
|
||||
Accept: []string{"四段式完整正文,无省略套话"},
|
||||
},
|
||||
{
|
||||
ID: "step_07_review",
|
||||
Role: "leader",
|
||||
Title: "终审并定稿",
|
||||
SubWorkspace: "tasks/07_review",
|
||||
Mode: "execute",
|
||||
Needs: []string{"tasks/06_draft/output.md"},
|
||||
Accept: []string{"质量把关并通过交付"},
|
||||
},
|
||||
}
|
||||
default:
|
||||
return []StepSpec{
|
||||
{
|
||||
ID: "step_01_draft",
|
||||
Role: "writer",
|
||||
Title: "撰写初稿",
|
||||
SubWorkspace: "tasks/01_draft",
|
||||
Mode: "execute",
|
||||
Accept: []string{"按模板四段式展开"},
|
||||
},
|
||||
{
|
||||
ID: "step_02_review",
|
||||
Role: "leader",
|
||||
Title: "审核定稿",
|
||||
SubWorkspace: "tasks/02_review",
|
||||
Mode: "execute",
|
||||
Needs: []string{"tasks/01_draft/output.md"},
|
||||
Accept: []string{"质量门禁通过"},
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func writeJSON(path string, val any) error {
|
||||
b, err := json.MarshalIndent(val, "", " ")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return os.WriteFile(path, b, 0o644)
|
||||
}
|
||||
Reference in New Issue
Block a user