Files
Agent-Native/runtime/guardrails/guardrails.go
T
weichen 948ca36ca2
ci / vet-and-test (pull_request) Successful in 47s
ci / docker-checkout-hostgateway (pull_request) Successful in 0s
Add AgentSpec, three guardrail ports, subagent events, and ask passthrough
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
2026-10-07 05:04:58 +00:00

95 lines
3.4 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 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
})
}
}