Files
Agent-Squad/squadintent/gate.go
T
weichen f3d000f6a6 Add the optional intent gate port with the squadintent adapter, CLI wiring, and pin cascade
Details (original message):
Add the optional intent gate port with the squadintent adapter, CLI wiring, and pin cascade.
2026-09-29 16:05:33 +00:00

115 lines
3.6 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// Package squadintent 把 agent-intent 的识别结果适配为 SquadEngine 的意图闸门。
//
// 本包是 Agent-Squad 仓内唯一 import agent-intent 的地方;squad/server/store 核心
// 不感知意图。未提供任何意图来源时 New 返回 nil 闸门(= 未启用),引擎行为与
// 未接入时完全一致;来源存在但配置非法时返回错误(调用方 fail-closed 拒绝启动)。
//
// 分支映射:
//
// GuardHit → Reject(引导文案取 Metadata["guidance"])
// Matched/ExpectationHit 且 Metadata["blueprint"] 非空 → 蓝图定向(等价显式 TaskKey)
// Matched 但无 blueprint 映射 / Ambiguous / Fallback → nil(不干预,走原路径)
// 识别系统错误 → error(引擎标记运行失败,fail-closed)
package squadintent
import (
"context"
"io/fs"
"agent-intent/intent"
"agent-squad/squad"
)
// Options 闸门构造选项。
type Options struct {
// IntentOptions 传给 agent-intent:GuardDomains/ProtectedDomains/上限等(Log 可选)。
IntentOptions intent.Options
// GuardSpecs 宿主内置守卫 spec,应位于 ProtectedDomains 声明的域内(overlay 不可覆盖)。
GuardSpecs []intent.IntentSpec
// FS 与 Roots 是 JSON overlay 来源;两者为空则只用 GuardSpecs。
FS fs.FS
Roots []string
}
// New 构造闸门。没有任何意图来源时返回 (nil, nil)——引擎拿到 nil IntentGate,
// 行为与原实现一致(T0 行为解耦)。
func New(ctx context.Context, o Options) (*Gate, error) {
if len(o.GuardSpecs) == 0 && (o.FS == nil || len(o.Roots) == 0) {
return nil, nil
}
b := intent.NewRegistryBuilder(o.IntentOptions)
if len(o.GuardSpecs) > 0 {
if err := b.Register(o.GuardSpecs...); err != nil {
return nil, err
}
}
var (
reg *intent.Registry
err error
)
if o.FS != nil {
reg, err = b.BuildFS(ctx, o.FS, o.Roots...)
} else {
reg, err = b.Build()
}
if err != nil {
return nil, err
}
return &Gate{rec: intent.NewRecognizer(o.IntentOptions, nil, nil), snap: reg.Snapshot()}, nil
}
// Gate 实现 squad.IntentGate。
type Gate struct {
rec intent.Recognizer
snap *intent.Snapshot
}
// Snapshot 暴露当前识别快照(诊断/审计用;Reload 由宿主经重建闸门实现)。
func (g *Gate) Snapshot() *intent.Snapshot {
if g == nil {
return nil
}
return g.snap
}
// Decide 实现 squad.IntentGate。同输入 + 同快照结果确定;无副作用。
func (g *Gate) Decide(ctx context.Context, goal squad.Goal, opts squad.RunOptions) (*squad.IntentDecision, error) {
if g == nil || g.snap == nil {
return nil, nil
}
if opts.TaskKey != "" {
return nil, nil // 显式 TaskKey 优先(引擎侧同样保证,双保险)
}
res, err := g.rec.Recognize(ctx, goal.Text, g.snap)
if err != nil {
return nil, err
}
switch res.Branch {
case intent.BranchGuardHit:
if res.Top == nil {
return nil, nil
}
return &squad.IntentDecision{
Reject: &squad.IntentRejection{
Reason: "命中守卫意图 " + res.Top.ID,
Guidance: res.Top.Metadata["guidance"],
},
IntentID: res.Top.ID,
Handler: res.Top.Handler,
Metadata: res.Top.Metadata,
}, nil
case intent.BranchMatched, intent.BranchExpectationHit:
if res.Top == nil || res.Top.Metadata["blueprint"] == "" {
return nil, nil // 命中但无蓝图映射:不干预
}
return &squad.IntentDecision{
BlueprintKey: res.Top.Metadata["blueprint"],
IntentID: res.Top.ID,
Handler: res.Top.Handler,
Metadata: res.Top.Metadata,
}, nil
default:
return nil, nil // Ambiguous / Fallback:交给原路径
}
}