Go 独立消费者¶
在自己的新应用中使用已发布的 Core 2.8.0,不需要检出 PRAXO-CORE 源码。
Starter 使用确定性模型和内存工具,无需凭据、模型服务或 AxisVela 产品,便能验证安装与授权行为。
是否适用?¶
| 任务 | 适用性与边界 |
|---|---|
| 在现有 Go 应用嵌入模型与工具循环 | 适用;应用负责模型适配器与工具 |
| 授权工具调用并验证拒绝路径 | 适用;应用提供策略与真实身份 |
| 接入人工审批或 Checkpoint 恢复 | 提供接口;持久审批和外部效果对账仍属集成职责 |
| 持久图工作流或 exactly-once 效果 | Core 单独不提供;需要专门运行时或执行服务 |
| 托管沙箱或机器人安全控制器 | 不提供;Core 提供集成边界,而非这些服务 |
选型前阅读何时不应选择 Core。
创建应用¶
使用 Go 1.26.11,即 CI/发布验证工具链。安装需要访问 Registry 或使用已有模块缓存。
全部命令应先设置 GOWORK=off,避免父目录工作区替换为本地依赖。
PowerShell 使用 $env:GOWORK = "off",POSIX Shell 使用 export GOWORK=off。
在新的应用目录运行:
go mod init example.com/praxo-starter
go get github.com/axisvela/praxo-core/v2@v2.8.0
应用源码¶
以下为 main.go。文档站直接从受测 Starter 源码
渲染,不另外维护代码副本。在 GitHub 阅读时可通过该链接查看完整源码。
package main
import (
"context"
"fmt"
"io"
"log"
"os"
"strings"
"github.com/axisvela/praxo-core/v2/pkg/agent"
"github.com/axisvela/praxo-core/v2/pkg/model"
"github.com/axisvela/praxo-core/v2/pkg/policy"
"github.com/axisvela/praxo-core/v2/pkg/tool"
)
type fixtureModel struct{ target string }
func (m fixtureModel) Generate(_ context.Context, req model.Request) (model.Response, error) {
last := req.Messages[len(req.Messages)-1]
if last.Role == model.RoleTool {
return model.Response{Message: model.Message{Role: model.RoleAssistant, Content: "recorded"}}, nil
}
return model.Response{Message: model.Message{
Role: model.RoleAssistant,
ToolCalls: []model.ToolCall{{
ID: "record-1", Name: "record", Arguments: map[string]any{"target": m.target},
}},
}}, nil
}
type recordTool struct{ calls int }
func (*recordTool) Name() string { return "record" }
func (*recordTool) Description() string { return "Record a target in an in-memory counter." }
func (*recordTool) Spec() model.ToolSpec {
return model.ToolSpec{
Name: "record", Description: "Record a target in an in-memory counter.",
Parameters: map[string]any{
"type": "object", "required": []string{"target"},
"properties": map[string]any{"target": map[string]any{"type": "string"}},
},
}
}
func (t *recordTool) Invoke(context.Context, map[string]any) (any, error) {
t.calls++
return "recorded", nil
}
func runScenario(ctx context.Context, target string) (string, int, error) {
impl := &recordTool{}
tools := tool.NewRegistry()
tools.Register(impl)
ag, err := agent.New(agent.Options{
Model: fixtureModel{target: target}, Tools: tools, MaxSteps: 3,
AllowedTools: []string{"record"},
Policy: policy.FuncHook(func(_ context.Context, action policy.Action) (policy.Decision, error) {
switch action.Kind {
case "model.generate":
return policy.Decision{Allowed: true}, nil
case "tool.invoke":
return policy.Decision{
Allowed: action.ToolName == "record" && action.Arguments["target"] == "public",
Reason: "only the public target is permitted",
}, nil
default:
return policy.Decision{Reason: "unsupported action"}, nil
}
}),
})
if err != nil {
return "", impl.calls, err
}
result, err := ag.Run(ctx, agent.Session{ID: "starter-" + target}, "Record the requested target.")
if err != nil {
return "", impl.calls, err
}
return result.Output, impl.calls, nil
}
func run(ctx context.Context, out io.Writer) error {
output, calls, err := runScenario(ctx, "public")
if err != nil || output != "recorded" || calls != 1 {
return fmt.Errorf("allow verification failed: output=%q effects=%d error=%v", output, calls, err)
}
if _, err := fmt.Fprintln(out, "allow: recorded; effects=1"); err != nil {
return err
}
_, calls, err = runScenario(ctx, "private")
if err == nil || !strings.Contains(err.Error(), "tool.invoke denied") || calls != 0 {
return fmt.Errorf("deny verification failed: effects=%d error=%v", calls, err)
}
_, err = fmt.Fprintln(out, "deny: policy rejected; effects=0")
return err
}
func main() {
if err := run(context.Background(), os.Stdout); err != nil {
log.Fatal(err)
}
}
验收测试¶
以下为 main_test.go,同样从受测源码
渲染。依赖检查拒绝模块替换,并验证精确 Core 版本。
package main
import (
"bytes"
"context"
"encoding/json"
"io"
"os"
"os/exec"
"strings"
"testing"
)
func TestAllowedTool(t *testing.T) {
output, calls, err := runScenario(context.Background(), "public")
if err != nil || output != "recorded" || calls != 1 {
t.Fatalf("output=%q effects=%d error=%v", output, calls, err)
}
}
func TestDeniedToolHasNoEffect(t *testing.T) {
output, calls, err := runScenario(context.Background(), "private")
if err == nil || !strings.Contains(err.Error(), "tool.invoke denied") {
t.Fatalf("expected policy denial, got %v", err)
}
if calls != 0 || output != "" {
t.Fatalf("denied call: output=%q effects=%d", output, calls)
}
}
func TestStarterOutput(t *testing.T) {
var out bytes.Buffer
if err := run(context.Background(), &out); err != nil {
t.Fatal(err)
}
if want := "allow: recorded; effects=1\ndeny: policy rejected; effects=0\n"; out.String() != want {
t.Fatalf("output=%q, want %q", out.String(), want)
}
}
func TestPublishedDependency(t *testing.T) {
cmd := exec.Command("go", "list", "-mod=readonly", "-m", "-json", "all")
cmd.Env = append(os.Environ(), "GOWORK=off")
output, err := cmd.Output()
if err != nil {
t.Fatal(err)
}
decoder := json.NewDecoder(bytes.NewReader(output))
found := false
for {
var dependency struct {
Path string
Version string
Replace json.RawMessage
}
if err := decoder.Decode(&dependency); err == io.EOF {
break
} else if err != nil {
t.Fatal(err)
}
if len(dependency.Replace) != 0 {
t.Fatalf("local replacement for %s", dependency.Path)
}
if dependency.Path == "github.com/axisvela/praxo-core/v2" {
found = true
if dependency.Version != "v2.8.0" {
t.Fatalf("Core version=%q, want v2.8.0", dependency.Version)
}
}
}
if !found {
t.Fatal("published Core dependency missing")
}
}
验证¶
添加两个文件后,解析依赖并验证应用:
go mod tidy
go test -mod=readonly ./...
go vet -mod=readonly ./...
go build -mod=readonly ./...
go run -mod=readonly .
程序预期输出:
allow: recorded; effects=1
deny: policy rejected; effects=0
Fake Model 不依赖策略结果,始终请求工具。显式 AllowedTools 限制工具名,Policy
额外限制目标参数。private 目标被拒绝后,不得增加效果计数。当前 API 的空 allowlist
不代表 deny-all。
治理验收¶
发布包升级¶
在源码检出目录运行 python scripts/test_go_upgrade.py -v,会创建一个全新消费者,先安装
tests/consumer/go.mod 固定的历史版本,再在同一应用中升级到包映射里的 Go 推荐版本。
测试拒绝模块替换、核对两个精确版本,并要求应用源码不变。历史阶段只检查基础允许/拒绝;
升级后运行五项治理负向检查、build、vet 和预期程序输出,不声称历史安全基线也通过更强检查。
CI 在 Linux、Windows、macOS 执行该检查。每次运行只证明该发布版本对和 fixture,不证明 未发布候选版本、任意版本升级、真实用户采用或生产留存。
负向检查¶
发布包测试还包含五项负向检查:
go test -mod=readonly -run Governed -v ./...
| 检查 | 必须结果 |
|---|---|
| 缺少必需 Policy | 应用守卫在运行前拒绝;工具零副作用 |
| 工具范围为空 | 应用守卫拒绝;工具零副作用 |
| Middleware 改变最终参数 | 最终操作重新授权;拒绝阻止副作用 |
| 审批编辑改变参数 | 编辑后的操作重新授权;拒绝阻止副作用 |
| Policy 服务错误 | 运行 fail closed;工具零副作用 |
测试位于受测源码。 守卫属于应用层:Core 保留普通 nil-policy 嵌入模式。这些检查不证明真实身份、持久审批、 效果对账或通用安全 Profile。
这些检查只证明该发布包 fixture,不证明真实模型质量、身份真实性、进程隔离、持久效果 或生产安全。连接真实模型请继续阅读端点快速开始与采用指南, 替换 Fake Model 后仍需保留拒绝测试。