Files
Agent-Native/runtime/subagent/subagent.go
T
weichen d5a98e3f18 Add the delegation port and delegate_task tool backed by host factories
Why: complete agents delegate; the kernel must not nest executions
itself (orchestration stays with the host per the agent-execution
standard), so the missing piece is a port plus a tool face.

Evidence: go vet ./... clean; go test ./... 14 packages ok. Cases:
nil factory registers nothing, full delegation field round-trip
(goal/inputs/write_scope/non_goals/done_criteria/deliverables) with
sorted file list in the result text, sub-agent failure and factory
error propagation, schema-required goal rejection, pre-canceled
delegations never starting, and adapter end-to-end wiring through
Options.Subagent.

Impact: new runtime/subagent package (Request/Result/Factory); the
delegation message set matches the agent-execution delegation clause
(sub-tasks derive no extra permissions; budget fields are host-set);
native.Options gains Subagent.

Refs: plan B8; standards/agent-execution.md delegation section.
2026-10-01 05:53:07 +00:00

104 lines
3.6 KiB
Go

// Package subagent 定义委派端口:内核不内置嵌套执行(那是编排层职责),
// 只提供工具面与信息集契约;宿主(产品 SpawnSubagent、小队引擎等)
// 注入 Factory 实现真实派生执行。未注入则 delegate_task 不注册(fail-closed)。
//
// 信息集对齐 agent-execution 标准的委派条款:目标、输入、写范围、非目标、
// 完成判据、预算与回传格式——子任务不能从父任务目标推导额外权限。
package subagent
import (
"context"
"encoding/json"
"fmt"
"sort"
"strings"
"time"
"agent-native/runtime/tools"
)
// Request 是一次委派的完整信息集(模型经工具参数逐项填写)。
type Request struct {
Goal string `json:"goal"`
Inputs string `json:"inputs,omitempty"`
WriteScope string `json:"write_scope,omitempty"`
NonGoals string `json:"non_goals,omitempty"`
DoneCriteria string `json:"done_criteria,omitempty"`
Deliverables []string `json:"deliverables,omitempty"`
// Deadline / MaxToolCalls 是宿主注入的预算余量(模型不可自行扩权)。
Deadline time.Time `json:"-"`
MaxToolCalls int `json:"-"`
}
// Result 是子代理的回传:至少包含结论文本;文件由宿主按写范围物化。
type Result struct {
Success bool
Text string
Files map[string][]byte
Err string
}
// Factory 由宿主注入;ctx 取消必须传播到子执行。
type Factory func(ctx context.Context, req Request) (Result, error)
type delegateTool struct{ factory Factory }
// Register 把 delegate_task 工具注册进注册表(factory 为 nil 时不注册)。
func Register(r *tools.Registry, factory Factory) {
if factory == nil {
return
}
r.Register(&delegateTool{factory: factory})
}
func (t *delegateTool) Name() string { return "delegate_task" }
func (t *delegateTool) Description() string {
return "Delegate a self-contained subtask to a fresh sub-agent. Fill every field: goal, inputs (with versions), write_scope (directory the sub-agent may write), non_goals, done_criteria, deliverables. The sub-agent cannot exceed your permissions; report back its conclusion and files."
}
func (t *delegateTool) Parameters() json.RawMessage {
return json.RawMessage(`{"type":"object","required":["goal","done_criteria"],"properties":{
"goal":{"type":"string"},
"inputs":{"type":"string"},
"write_scope":{"type":"string"},
"non_goals":{"type":"string"},
"done_criteria":{"type":"string"},
"deliverables":{"type":"array","items":{"type":"string"}}
}}`)
}
func (t *delegateTool) Execute(ctx context.Context, args json.RawMessage) (tools.Result, error) {
var req Request
if err := json.Unmarshal(args, &req); err != nil {
return tools.Result{}, err
}
if strings.TrimSpace(req.Goal) == "" {
return tools.Result{Content: "goal is empty", IsError: true}, nil
}
if ctx.Err() != nil {
return tools.Result{Content: "delegation canceled before start: " + ctx.Err().Error(), IsError: true}, nil
}
res, err := t.factory(ctx, req)
if err != nil {
return tools.Result{Content: fmt.Sprintf("sub-agent failed: %v", err), IsError: true}, nil
}
var b strings.Builder
if res.Success {
b.WriteString("[sub-agent succeeded]\n")
} else {
b.WriteString("[sub-agent failed]\n")
}
if res.Err != "" {
b.WriteString("error: " + res.Err + "\n")
}
b.WriteString(res.Text)
if len(res.Files) > 0 {
names := make([]string, 0, len(res.Files))
for name := range res.Files {
names = append(names, name)
}
sort.Strings(names)
b.WriteString("\nfiles: " + strings.Join(names, ", "))
}
return tools.Result{Content: b.String(), IsError: !res.Success}, nil
}