Why: F05 - files-presence could promote failures to success, recovery marked crashed steps completed on files alone or reran them blindly, and retries minted a fresh ExecutionID each attempt. Evidence: squad tests pass incl. executor result contract and recovery tables (verify-u3b.json); (nil,nil), canceled and side-effect-possible semantics handled; interrupted steps without a trusted terminal now hold for reconciliation instead of auto-advancing. Impact: retries reuse the step ExecutionID and shared deadline; no auto-replay of unverified external actions. Refs: feature/agent-stack-audit-remediation-b1 U3B
347 lines
16 KiB
Go
347 lines
16 KiB
Go
// Package squad 提供通用智能体小队编排底座:角色注册、目标规划、动态执行、
|
||
// 黑板协同与 HITL 挂起/恢复。本包不含任何预设角色或任务模板。
|
||
package squad
|
||
|
||
import "time"
|
||
|
||
// RoleSource 标识角色来源。
|
||
type RoleSource string
|
||
|
||
const (
|
||
// RoleSourceRegistered 表示外部注册的角色(配置文件/API/CLI)。
|
||
RoleSourceRegistered RoleSource = "registered"
|
||
// RoleSourceDerived 表示规划器或用户在运行期派生的特设角色。
|
||
RoleSourceDerived RoleSource = "derived"
|
||
)
|
||
|
||
// RoleSpec 声明一个小队角色的完整设定。空表起步:库不预置任何角色。
|
||
// BashPermission 三态(执行类工具的授权档位)。
|
||
const (
|
||
BashPermissionAllow = "allow" // 装配执行工具,无人工门
|
||
BashPermissionAsk = "ask" // 装配执行工具,步骤前 HITL permission 门
|
||
BashPermissionDeny = "deny" // 不装配执行工具(默认)
|
||
)
|
||
|
||
type RoleSpec struct {
|
||
Role string `json:"role"`
|
||
Name string `json:"name"`
|
||
Source RoleSource `json:"source,omitempty"`
|
||
BaseRole string `json:"base_role,omitempty"`
|
||
Backend string `json:"backend,omitempty"` // 绑定的智能体名:stub/llm/agent-native/pi-agent/自定义注册名
|
||
Model string `json:"model,omitempty"` // 角色级模型(backend 支持时生效)
|
||
ToolProfile string `json:"tool_profile,omitempty"` // agent-native ToolProfile:workspace/reasoning
|
||
Tools []string `json:"tools,omitempty"`
|
||
Skills []string `json:"skills,omitempty"` // 技能标识(backend 支持时注入)
|
||
BashPermission string `json:"bash_permission"` // allow / ask / deny
|
||
Responsibilities []string `json:"responsibilities,omitempty"`
|
||
ForbiddenActions []string `json:"forbidden_actions,omitempty"`
|
||
Keywords []string `json:"keywords,omitempty"` // 规则规划器推荐与匹配用
|
||
Instructions string `json:"instructions,omitempty"` // 内联指令(优先)
|
||
InstructionsFile string `json:"instructions_file,omitempty"` // 指令文件(数据目录相对或绝对路径)
|
||
HitlPolicy *HitlPolicy `json:"hitl_policy,omitempty"` // 角色级 HITL 策略(覆盖全局)
|
||
// MaxConcurrentTasks 该角色同时执行步骤的上限(<=1 视为 1);
|
||
// 引擎在解析后端时以并发限流包装实现(对象级串行语义)。
|
||
MaxConcurrentTasks int `json:"max_concurrent_tasks,omitempty"`
|
||
}
|
||
|
||
// Goal 一次小队运行的目标输入。
|
||
type Goal struct {
|
||
Text string `json:"text"`
|
||
Materials []MaterialFile `json:"materials,omitempty"`
|
||
Constraints map[string]string `json:"constraints,omitempty"`
|
||
HitlMode HitlMode `json:"hitl_mode,omitempty"` // 覆盖全局策略
|
||
HitlTimeout time.Duration `json:"hitl_timeout,omitempty"`
|
||
}
|
||
|
||
// MaterialFile 一份输入材料(文件字节或磁盘路径二选一)。
|
||
type MaterialFile struct {
|
||
Filename string `json:"filename"`
|
||
Path string `json:"path,omitempty"`
|
||
Content []byte `json:"content,omitempty"`
|
||
}
|
||
|
||
// Blueprint 已注册的任务模板:命中的 Goal 走基线工作流,未注册走自主规划。
|
||
type Blueprint struct {
|
||
Key string `json:"key"` // 模板键(可由 BlueprintMatcher 匹配 Goal.Text)
|
||
Title string `json:"title"`
|
||
Match []string `json:"match,omitempty"` // 关键词命中即视为命中模板
|
||
Description string `json:"description,omitempty"`
|
||
// Topology 声明模板的拓扑(Q2;空 = standard 图行为)。
|
||
// RunOptions.Topology 优先于本字段。
|
||
Topology string `json:"topology,omitempty"`
|
||
Agents []RoleSpec `json:"agents,omitempty"` // 默认编制(引用已注册角色或内联角色)
|
||
Steps []StepSpec `json:"steps"`
|
||
Template string `json:"template,omitempty"` // 交付物骨架模板(Markdown)
|
||
}
|
||
|
||
// PlanningMode 规划模式。
|
||
type PlanningMode string
|
||
|
||
const (
|
||
PlanModeBlueprint PlanningMode = "blueprint" // 命中模板:基线步骤 + 动态微调
|
||
PlanModeAutonomous PlanningMode = "autonomous" // 未命中模板:规划器自主拆解
|
||
)
|
||
|
||
// StepSpec 单个步骤规格。
|
||
type StepSpec struct {
|
||
ID string `json:"id"`
|
||
Role string `json:"role"`
|
||
Title string `json:"title"`
|
||
SubWorkspace string `json:"sub_workspace,omitempty"` // 相对子路径,缺省 tasks/NN_role
|
||
Mode string `json:"mode,omitempty"` // 预留
|
||
Parallel bool `json:"parallel"`
|
||
Needs []string `json:"needs,omitempty"`
|
||
// Writes 写集:本步骤会写入的工件相对路径(调度依据)。
|
||
// 同一 superstep 内写集相交的步骤强制串行,不相交才可并行(Q1 WP1)。
|
||
Writes []string `json:"writes,omitempty"`
|
||
Targets []string `json:"targets,omitempty"` // 期望交付物相对路径,缺省 output.md
|
||
Status string `json:"status"` // pending, running, completed, failed, skipped
|
||
// Attempts 该步骤进入 running 的次数。中断恢复用它决定 auto 策略是否还能重试。
|
||
Attempts int `json:"attempts,omitempty"`
|
||
// AttemptID 当前这次尝试的稳定标识:runID/stepID/N(N=Attempts)。
|
||
// 执行事件、产物与副作用记录都挂它;瞬态重试共享同一 AttemptID(Q1 WP3)。
|
||
AttemptID string `json:"attempt_id,omitempty"`
|
||
DurationMs int64 `json:"duration_ms,omitempty"`
|
||
Error string `json:"error,omitempty"`
|
||
// RequiresApproval 为 true 时,该步骤启动前进入 HITL 计划确认。
|
||
RequiresApproval bool `json:"requires_approval,omitempty"`
|
||
// IntentID 非空 = 系统功能步:不经 BackendResolver/智能体,由 IntentRunner 直接执行
|
||
//(计划确定性校验:意图须在目录内且必填槽位齐)。
|
||
IntentID string `json:"intent_id,omitempty"`
|
||
// IntentSlots 系统功能步的槽位取值(规划者按目录填写)。
|
||
IntentSlots map[string]string `json:"intent_slots,omitempty"`
|
||
// AllowDynamicAdjustment 允许评估器在步骤后做动态调整(加步/跳步/改步)。
|
||
AllowDynamicAdjustment bool `json:"allow_dynamic_adjustment,omitempty"`
|
||
// Critical: nil(默认)= 关键步骤失败即终止;false = 非关键,失败降级继续。
|
||
Critical *bool `json:"critical,omitempty"`
|
||
// Skills 覆盖角色默认技能。空 = 用角色自己的技能列表。
|
||
Skills []string `json:"skills,omitempty"`
|
||
// MinScore 质检总分下限。0 = 不额外设门槛,只看 Checker.Passed。
|
||
MinScore int `json:"min_score,omitempty"`
|
||
// Loop 非空 = 循环步(Q2 loop 拓扑):完成后条件不满足且预算未尽时
|
||
// 重置为 pending 再武装(图不允许环,循环由引擎再武装实现)。
|
||
Loop *LoopSpec `json:"loop,omitempty"`
|
||
// LoopRuns 已再武装次数(引擎维护,随计划持久化)。
|
||
LoopRuns int `json:"loop_runs,omitempty"`
|
||
// Branches 非空 = 路由步(Q2 router 拓扑):完成后按黑板字段判定,
|
||
// 未选中分支的目标步骤置 skipped(skipped=依赖已满足,join 步照常推进)。
|
||
Branches []Branch `json:"branches,omitempty"`
|
||
// Handoff 非空 = 完成即把最终答案所有权移交给该角色(Q2 handoff 原语)。
|
||
Handoff string `json:"handoff,omitempty"`
|
||
// Brief 编译期任务指令(Q3 编译器):非空时渲染进 TASK.md 的
|
||
// 「任务指令」节,位于标题/产出之后、上下文表之前。图步的富文本任务书
|
||
// 载体(此前只有 leader 委派有 briefing)。
|
||
Brief string `json:"brief,omitempty"`
|
||
}
|
||
|
||
// LoopSpec 是循环步的预算与停机条件(Q2)。
|
||
type LoopSpec struct {
|
||
// MaxIterations 再武装上限(必须 >0;触顶即停,带 loop_exhausted 事件)。
|
||
MaxIterations int `json:"max_iterations"`
|
||
// UntilField 停机判据的黑板字段名;空 = 只按 MaxIterations 迭代。
|
||
UntilField string `json:"until_field,omitempty"`
|
||
// UntilEquals 停机值:Fields[UntilField] == UntilEquals 时停。
|
||
UntilEquals string `json:"until_equals,omitempty"`
|
||
// Body 与本步一起再武装的其他步骤 ID(循环体;如 evaluator-optimizer
|
||
// 的评估步)。空 = 只再武装本步。
|
||
Body []string `json:"body,omitempty"`
|
||
}
|
||
|
||
// Branch 是路由步的一条分支(Q2)。When 按声明序取首个命中。
|
||
type Branch struct {
|
||
When BranchCond `json:"when"`
|
||
// Then 是本分支保留的步骤 ID(其余分支目标置 skipped)。
|
||
Then []string `json:"then"`
|
||
}
|
||
|
||
// BranchCond 是分支判据:黑板字段等值/不等值。
|
||
type BranchCond struct {
|
||
Field string `json:"field"`
|
||
Equals string `json:"equals,omitempty"`
|
||
NotEquals string `json:"not_equals,omitempty"`
|
||
}
|
||
|
||
// IsCritical 报告步骤是否关键(nil 视为关键)。
|
||
func (s StepSpec) IsCritical() bool { return s.Critical == nil || *s.Critical }
|
||
|
||
// WorkflowPlan 编排清单(与产品 squad workflow.json 字段兼容)。
|
||
type WorkflowPlan struct {
|
||
WorkflowID string `json:"workflow_id"`
|
||
TaskKey string `json:"task_key,omitempty"` // 命中模板时为模板键
|
||
Title string `json:"title,omitempty"`
|
||
PlanningMode PlanningMode `json:"planning_mode,omitempty"`
|
||
Status string `json:"status"` // pending, in_progress, completed, failed, degraded, cancelled
|
||
CurrentStepIndex int `json:"current_step_index"`
|
||
Steps []StepSpec `json:"steps"`
|
||
}
|
||
|
||
// SquadConfig 激活的小队编制(与产品 squad.json 字段兼容)。
|
||
type SquadConfig struct {
|
||
SquadName string `json:"squad_name"`
|
||
Version string `json:"version"`
|
||
Agents []RoleSpec `json:"agents"`
|
||
}
|
||
|
||
// ---- 黑板 ----
|
||
|
||
// FactItem 黑板事实项。
|
||
type FactItem struct {
|
||
ID string `json:"id"`
|
||
Claim string `json:"claim"`
|
||
Source string `json:"source"`
|
||
Verified bool `json:"verified"`
|
||
}
|
||
|
||
// GapItem 黑板缺口项;Blocking=true 且未解决时触发 HITL 挂起。
|
||
// WaitRunID/WaitField 非空时,该缺口的 HITL 请求携带等待条件:
|
||
// 目标运行进入终态 / 黑板字段出现时自动唤醒续跑(等事件而非只等人)。
|
||
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"`
|
||
WaitRunID string `json:"wait_run_id,omitempty"` // 等待指定运行到达终态
|
||
WaitField string `json:"wait_field,omitempty"` // 等待黑板出现该字段
|
||
}
|
||
|
||
// ImplicationItem 黑板推导项。
|
||
type ImplicationItem struct {
|
||
ID string `json:"id"`
|
||
Topic string `json:"topic"`
|
||
Statement string `json:"statement"`
|
||
}
|
||
|
||
// AssumptionItem 黑板假设项(auto HITL 模式下作为阻塞缺口的兜底假设来源)。
|
||
type AssumptionItem struct {
|
||
ID string `json:"id"`
|
||
Topic string `json:"topic"`
|
||
Statement string `json:"statement"`
|
||
Rationale string `json:"rationale"`
|
||
Source string `json:"source"` // 默认 inferred;HITL 兜底时为 fallback
|
||
}
|
||
|
||
// BlackboardState 结构化共享黑板(与产品 blackboard.json 字段兼容)。
|
||
type BlackboardState struct {
|
||
Facts []FactItem `json:"facts"`
|
||
Gaps []GapItem `json:"gaps"`
|
||
Implications []ImplicationItem `json:"implications"`
|
||
Assumptions []AssumptionItem `json:"assumptions,omitempty"`
|
||
Fields map[string]string `json:"fields"`
|
||
UpdatedAt time.Time `json:"updated_at"`
|
||
}
|
||
|
||
// ContextItem 工作区上下文资源条目。
|
||
type ContextItem struct {
|
||
File string `json:"file"`
|
||
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"`
|
||
}
|
||
|
||
// QualityReport 交付检查报告。
|
||
type QualityReport struct {
|
||
Passed bool `json:"passed"`
|
||
Score float64 `json:"score"`
|
||
Violations []string `json:"violations,omitempty"`
|
||
MissingSections []string `json:"missing_sections,omitempty"`
|
||
Placeholders []string `json:"placeholders,omitempty"`
|
||
// ManualPass 为 true 时表示质检未过但经 HITL 人工放行。
|
||
ManualPass bool `json:"manual_pass,omitempty"`
|
||
CheckedAt time.Time `json:"checked_at"`
|
||
}
|
||
|
||
// Manifest 执行清单与防篡改审计。
|
||
type Manifest struct {
|
||
ManifestVersion string `json:"manifest_version"`
|
||
WorkspaceID string `json:"workspace_id"`
|
||
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"`
|
||
}
|
||
|
||
// RunMetadata 工作区元信息(workspace.json)。
|
||
type RunMetadata struct {
|
||
ID string `json:"id"`
|
||
Goal Goal `json:"goal"`
|
||
Status string `json:"status"`
|
||
Backend string `json:"backend,omitempty"` // 默认后端名(角色级 Backend 优先)
|
||
Planner string `json:"planner,omitempty"`
|
||
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"`
|
||
}
|
||
|
||
// RunStatus 一次运行的对外状态(含 HITL 挂起态)。
|
||
type RunStatus string
|
||
|
||
const (
|
||
RunCreated RunStatus = "created"
|
||
RunPlanning RunStatus = "planning"
|
||
RunAwaitingClarification RunStatus = "awaiting_clarification" // HITL:规划歧义,等待澄清
|
||
RunAwaitingConfirmation RunStatus = "awaiting_confirmation" // HITL:计划待确认
|
||
RunExecuting RunStatus = "executing"
|
||
RunWaitingHuman RunStatus = "waiting_human" // HITL:执行中挂起(缺口/审批/权限/质检)
|
||
RunQualityReview RunStatus = "quality_review" // HITL:质检超限转人工
|
||
RunCompleted RunStatus = "completed"
|
||
RunDegraded RunStatus = "degraded"
|
||
RunFailed RunStatus = "failed"
|
||
RunCancelled RunStatus = "cancelled"
|
||
// RunWaitingReconciliation F05:recovery 期间无法判定步骤结果(无 trusted
|
||
// terminal record、文件存在性与 record 冲突等);非交互模式下记录该状态
|
||
// 而非永久挂起。交互模式下挂起(合成 HITL 门),与既有 HITL 通道同款语义。
|
||
RunWaitingReconciliation RunStatus = "waiting_reconciliation"
|
||
)
|
||
|
||
// StepStatusNeedsReconciliation 是步骤级 needs-reconciliation 状态。
|
||
// 仅供 recovery 使用;runStep 不在成功路径写入。
|
||
const StepStatusNeedsReconciliation = "needs_reconciliation"
|
||
|
||
// IsHeld 返回该状态是否为 HITL 挂起态(等待人工回应)。
|
||
func (s RunStatus) IsHeld() bool {
|
||
return s == RunAwaitingClarification || s == RunAwaitingConfirmation ||
|
||
s == RunWaitingHuman || s == RunQualityReview ||
|
||
s == RunWaitingReconciliation
|
||
}
|
||
|
||
// Terminal 报告该状态是否为终态。
|
||
func (s RunStatus) Terminal() bool {
|
||
switch s {
|
||
case RunCompleted, RunDegraded, RunFailed, RunCancelled:
|
||
return true
|
||
}
|
||
return false
|
||
}
|
||
|
||
// RunResult 引擎单次推进的返回(Run/Respond/Resume 共用)。
|
||
type RunResult struct {
|
||
RunID string `json:"run_id"`
|
||
Status RunStatus `json:"status"`
|
||
Plan *WorkflowPlan `json:"plan,omitempty"`
|
||
Squad *SquadConfig `json:"squad,omitempty"`
|
||
Pending *HumanRequest `json:"pending,omitempty"` // 挂起时的人工请求
|
||
Deliverable string `json:"deliverable,omitempty"`
|
||
Quality *QualityReport `json:"quality,omitempty"`
|
||
Summary string `json:"summary,omitempty"`
|
||
Error string `json:"error,omitempty"`
|
||
WorkDir string `json:"work_dir,omitempty"`
|
||
// Usage 按角色聚合的模型用量(步骤执行结果累计)。
|
||
Usage map[string]Usage `json:"usage,omitempty"`
|
||
}
|
||
|
||
// Usage 单角色的 token 用量(多步骤累计)。
|
||
type Usage struct {
|
||
PromptTokens int `json:"prompt_tokens"`
|
||
CompletionTokens int `json:"completion_tokens"`
|
||
Steps int `json:"steps"`
|
||
}
|