Why: N1 increment three (WP1/WP4) - agents need a canonical spec format with plugin restrictions, hosts need typed input/output/tool guardrails, delegation needs lifecycle events, and squad adapters need to pass the permission ask port through. Evidence: go vet clean; go test ./... 26 packages ok, including new agentspec (12 tests), guardrails (5), loop_guardrails (5), and subagent hook tests (3). Impact: new runtime/agentspec and runtime/guardrails packages; loop consumes UserPromptSubmit outcomes (deny skips the loop, modify appends the rewrite); subagent gains RegisterWithHooks emitting SubagentStart/Stop around delegation; GateOptions gains PermissionAsk forwarded into the kernel config. All additions are opt-in: nil ports preserve prior behavior exactly. Refs: Documents proposal 2026-10-07-agent-stack-n1q1i1-plans.md WP1/WP4
95 lines
3.4 KiB
Go
95 lines
3.4 KiB
Go
// Package guardrails 提供三类语义化护栏端口:输入(只作用于入口智能体)、
|
||
// 输出(只作用于给出最终结果的智能体)、工具(每次工具调用)。
|
||
// 护栏以钩子总线注册:它是类型化语义层,钩子仍是通用事件层。
|
||
// 当前形态护栏拿不到运行 ctx(事件载荷不含它);需要取消语义时直接写钩子。
|
||
package guardrails
|
||
|
||
import (
|
||
"context"
|
||
|
||
"agent-native/runtime/hooks"
|
||
)
|
||
|
||
// Verdict 是一次护栏表态。空 Decision 表示不插手。
|
||
// Deny 拒绝(Output 层表现为要求模型改进后继续);Modify 改写载荷
|
||
// (Input 改用户输入、Output 改最终文本、Tool 改参数);Reason 附说明。
|
||
type Verdict struct {
|
||
Decision hooks.Decision
|
||
Reason string
|
||
Replacement string
|
||
}
|
||
|
||
type InputGuardrail func(ctx context.Context, text string) Verdict
|
||
type OutputGuardrail func(ctx context.Context, text string) Verdict
|
||
type ToolGuardrail func(ctx context.Context, name, args string) Verdict
|
||
|
||
// Set 是一组护栏;nil 字段表示该层不启用。
|
||
type Set struct {
|
||
Input InputGuardrail
|
||
Output OutputGuardrail
|
||
Tool ToolGuardrail
|
||
}
|
||
|
||
// Scope 标注本 run 的角色:Entry=本 run 处理入口用户输入;
|
||
// Final=本 run 给出最终答案。子代理 run 两者都为 false,
|
||
// Input/Output 护栏不会作用于它;Tool 护栏作用于一切工具调用。
|
||
type Scope struct {
|
||
Entry bool
|
||
Final bool
|
||
}
|
||
|
||
// Register 把护栏挂到总线。语义:
|
||
// - Input(仅 scope.Entry):挂 UserPromptSubmit。deny → 循环不启动,
|
||
// 以理由直接收尾;modify → 改写后的输入追加为新的用户消息(原文留痕)。
|
||
// - Output(仅 scope.Final):挂 Stop。deny → 视同续跑,理由交给模型改进
|
||
// (计入内核续跑上限,达上限后收尾并附 Notice);modify → 替换最终文本并续跑。
|
||
// - Tool:挂 PreToolUse,与其他钩子按既有优先级合并
|
||
// (deny/block > ask > modify > allow);modify 的 Replacement 改写工具参数。
|
||
func Register(bus *hooks.Bus, set Set, scope Scope) {
|
||
if bus == nil {
|
||
return
|
||
}
|
||
if set.Input != nil && scope.Entry {
|
||
g := set.Input
|
||
bus.On(func(c hooks.Call) hooks.Outcome {
|
||
if c.Event != hooks.UserPromptSubmit {
|
||
return hooks.Outcome{}
|
||
}
|
||
v := g(context.Background(), c.LastText)
|
||
return hooks.Outcome{Decision: v.Decision, Reason: v.Reason, Replacement: v.Replacement}
|
||
})
|
||
}
|
||
if set.Output != nil && scope.Final {
|
||
g := set.Output
|
||
bus.On(func(c hooks.Call) hooks.Outcome {
|
||
if c.Event != hooks.Stop {
|
||
return hooks.Outcome{}
|
||
}
|
||
v := g(context.Background(), c.LastText)
|
||
switch v.Decision {
|
||
case hooks.Deny, hooks.Block:
|
||
// 对 Stop 事件而言「拒绝最终答案」= 要求模型改进后再来一轮。
|
||
return hooks.Outcome{Decision: hooks.Block, Reason: v.Reason}
|
||
case hooks.Modify:
|
||
return hooks.Outcome{Decision: hooks.Block, Reason: v.Reason, Replacement: v.Replacement}
|
||
default:
|
||
return hooks.Outcome{Decision: v.Decision, Reason: v.Reason}
|
||
}
|
||
})
|
||
}
|
||
if set.Tool != nil {
|
||
g := set.Tool
|
||
bus.On(func(c hooks.Call) hooks.Outcome {
|
||
if c.Event != hooks.PreToolUse {
|
||
return hooks.Outcome{}
|
||
}
|
||
v := g(context.Background(), c.Tool, c.Args)
|
||
out := hooks.Outcome{Decision: v.Decision, Reason: v.Reason}
|
||
if v.Decision == hooks.Modify {
|
||
out.Args = v.Replacement
|
||
}
|
||
return out
|
||
})
|
||
}
|
||
}
|