Project the command types through the intent gate surface #76

Merged
weichen merged 1 commits from feature/agent-stack-i1 into main 2026-10-07 07:59:08 +00:00
3 changed files with 36 additions and 24 deletions
+17 -1
View File
@@ -26,9 +26,25 @@ type MatchResult struct {
Missing []SlotQuestion
// Commands 是库层展开的命令列表(I1);Router 消费它做唯一裁决。
// guard_hit/expectation_hit 被折叠为 fallback 时为空。
Commands []agentintent.Command
Commands []Command
}
// 命令类型经闸门投影(别名):生态库按边界规则只许本包导入,
// Router/runmgr 等消费方只依赖本包表面。
type (
Command = agentintent.Command
ActionRef = agentintent.ActionRef
)
const (
CommandStart = agentintent.CommandStart
ActionAuto = agentintent.ActionAuto
ActionCommand = agentintent.ActionCommand
ActionWorkflow = agentintent.ActionWorkflow
ActionBlueprint = agentintent.ActionBlueprint
ActionHandler = agentintent.ActionHandler
)
type Gate struct {
mu sync.RWMutex
reg *agentintent.Registry
+6 -8
View File
@@ -19,8 +19,6 @@
package route
import (
agentintent "agent-intent/intent"
"agent-system-backend/internal/intentgate"
core "agent-system-core"
)
@@ -64,14 +62,14 @@ func Decide(res intentgate.MatchResult, env Env) Decision {
if res.Branch != "matched" {
return d
}
var start *agentintent.Command
var start *intentgate.Command
for i := range res.Commands {
if res.Commands[i].Kind == agentintent.CommandStart {
if res.Commands[i].Kind == intentgate.CommandStart {
start = &res.Commands[i]
break
}
}
action := agentintent.ActionRef{Kind: agentintent.ActionAuto, ID: res.ID}
action := intentgate.ActionRef{Kind: intentgate.ActionAuto, ID: res.ID}
if start != nil {
d.StartFound = true
action = start.Action
@@ -80,13 +78,13 @@ func Decide(res intentgate.MatchResult, env Env) Decision {
}
}
switch action.Kind {
case agentintent.ActionCommand:
case intentgate.ActionCommand:
if env.HasCommand != nil && env.HasCommand(action.ID) {
d.Kind, d.CommandID = KindCommand, action.ID
return d
}
d.Reason = "声明的命令不存在:" + action.ID
case agentintent.ActionWorkflow:
case intentgate.ActionWorkflow:
if env.WorkflowFor != nil {
if def, ok := env.WorkflowFor(action.ID); ok {
d.Kind, d.Workflow = KindWorkflow, def
@@ -94,7 +92,7 @@ func Decide(res intentgate.MatchResult, env Env) Decision {
}
}
d.Reason = "声明的工作流未注册:" + action.ID
case agentintent.ActionBlueprint:
case intentgate.ActionBlueprint:
if env.SquadReady {
d.Kind, d.Blueprint = KindSquad, action.ID
return d
+13 -15
View File
@@ -3,19 +3,17 @@ package route
import (
"testing"
agentintent "agent-intent/intent"
"agent-system-backend/internal/intentgate"
"agent-system-backend/internal/intentgate"
core "agent-system-core"
)
func startCmd(action agentintent.ActionRef) agentintent.Command {
return agentintent.Command{Kind: agentintent.CommandStart, Action: action}
func startCmd(action intentgate.ActionRef) intentgate.Command {
return intentgate.Command{Kind: intentgate.CommandStart, Action: action}
}
func TestDecideAutoPrefersCommandThenWorkflow(t *testing.T) {
in := intentgate.MatchResult{Branch: "matched", ID: "demo.x",
Commands: []agentintent.Command{startCmd(agentintent.ActionRef{Kind: agentintent.ActionAuto, ID: "demo.x"})}}
Commands: []intentgate.Command{startCmd(intentgate.ActionRef{Kind: intentgate.ActionAuto, ID: "demo.x"})}}
d := Decide(in, Env{HasCommand: func(string) bool { return true }})
if d.Kind != KindCommand || d.CommandID != "demo.x" {
t.Fatalf("auto command = %+v", d)
@@ -36,26 +34,26 @@ func TestDecideAutoPrefersCommandThenWorkflow(t *testing.T) {
func TestDecideExplicitKinds(t *testing.T) {
mk := func(kind, id string) intentgate.MatchResult {
return intentgate.MatchResult{Branch: "matched", ID: "demo.x",
Commands: []agentintent.Command{startCmd(agentintent.ActionRef{Kind: kind, ID: id})}}
Commands: []intentgate.Command{startCmd(intentgate.ActionRef{Kind: kind, ID: id})}}
}
if d := Decide(mk(agentintent.ActionCommand, "cmd-1"), Env{HasCommand: func(id string) bool { return id == "cmd-1" }}); d.Kind != KindCommand || d.CommandID != "cmd-1" {
if d := Decide(mk(intentgate.ActionCommand, "cmd-1"), Env{HasCommand: func(id string) bool { return id == "cmd-1" }}); d.Kind != KindCommand || d.CommandID != "cmd-1" {
t.Fatalf("explicit command = %+v", d)
}
d := Decide(mk(agentintent.ActionCommand, "nope"), Env{
d := Decide(mk(intentgate.ActionCommand, "nope"), Env{
WorkflowFor: func(string) (core.WorkflowDef, bool) { return core.WorkflowDef{ID: "wf"}, true },
})
if d.Kind != KindTurn || d.Reason == "" {
t.Fatalf("unresolvable command = %+v", d)
}
if d := Decide(mk(agentintent.ActionWorkflow, "wf-1"), Env{
if d := Decide(mk(intentgate.ActionWorkflow, "wf-1"), Env{
WorkflowFor: func(id string) (core.WorkflowDef, bool) { return core.WorkflowDef{ID: id}, true },
}); d.Kind != KindWorkflow || d.Workflow.ID != "wf-1" {
t.Fatalf("explicit workflow = %+v", d)
}
if d := Decide(mk(agentintent.ActionBlueprint, "bp-1"), Env{SquadReady: true}); d.Kind != KindSquad || d.Blueprint != "bp-1" {
if d := Decide(mk(intentgate.ActionBlueprint, "bp-1"), Env{SquadReady: true}); d.Kind != KindSquad || d.Blueprint != "bp-1" {
t.Fatalf("blueprint = %+v", d)
}
if d := Decide(mk(agentintent.ActionBlueprint, "bp-1"), Env{}); d.Kind != KindTurn || d.Reason == "" {
if d := Decide(mk(intentgate.ActionBlueprint, "bp-1"), Env{}); d.Kind != KindTurn || d.Reason == "" {
t.Fatalf("blueprint without squad = %+v", d)
}
}
@@ -73,9 +71,9 @@ func TestDecideNonMatchedAndLegacyFallback(t *testing.T) {
func TestSlotsFromStartCommand(t *testing.T) {
in := intentgate.MatchResult{Branch: "matched", ID: "demo.x",
Commands: []agentintent.Command{{
Kind: agentintent.CommandStart,
Action: agentintent.ActionRef{Kind: agentintent.ActionAuto, ID: "demo.x"},
Commands: []intentgate.Command{{
Kind: intentgate.CommandStart,
Action: intentgate.ActionRef{Kind: intentgate.ActionAuto, ID: "demo.x"},
Slots: map[string]string{"topic": "电机"},
}}}
d := Decide(in, Env{HasCommand: func(string) bool { return true }})