Jig 技术路线:从 Agent 编排框架到预执行安全门禁的 TaoToken 实践
发布时间:2026/10/4 16:57:15 锦皓数字建站

1. 从编排框架到预执行门禁Jig 到底在解决什么问题Jig 是一个把「Agent 编排」和「工具调用前拦截」合并到同一层的框架核心能力是 ToolGuard 预执行安全门禁——在工具真正被调用之前用代码判断这次调用该不该放行。它适合两类人一类是已经在用 LangGraph、CrewAI 或 OpenAI Agents SDK 搭过多 Agent 流程但发现安全边界只能靠 prompt 劝说的开发者另一类是想给 Claude Code、Codex 这类外部 Agent 加一层统一管控的团队。我最初接触 Jig 是因为一个很具体的痛点管道里有个 Coding Agent 会调用 Bash某次它把一条带通配符的删除命令拼进了参数里虽然最后没执行成功但整个过程没有任何一层能拦住它——prompt 里写了「不要执行危险命令」模型该拼还是拼。事后复盘发现所有主流框架的安全机制都停在「劝」的层面没有「拦」的层面。Jig 的路线图从 v0.1 就把 ToolGuard 定型成代码级阻断接口这个接口到 v0.6 一行没改这种设计稳定性在快速迭代的 Agent 赛道里很少见。它的技术路线可以概括成一条主线v0.1 用 SKILL.md 声明式定义 Agent同时把 ToolGuard 的 check 接口固定下来v0.2 补并行编排和检查点先保证崩溃可恢复再谈长时间运行v0.4 打通 PM→Spec→Coding→Acceptance 四节点管道ToolGuard 升级成三层硬约束vA.0.2-3 加入四层记忆和 CircuitBreaker 三态熔断v0.5 从 DeepSeek-only 扩展到多模型并支持 SSE 流式v0.6 引入 GraphOrchestrator 和 LoopEngine 收敛检测把线性 SOP 变成 DAG。真正让它区别于「又一个编排框架」的是四层架构里的 Control PlaneToolGuard、LOOP SOP、GlobalConstraints、CircuitBreaker 全部放在 Agent 执行之前。Agent Plane 负责解析 Skill、注册、工厂化生产 AgentOrchestration Plane 管 SOPRunner、Graph、LoopEngine、Memory、CheckpointTool Plane 对接 MCP、ModelRouter、CacheEngine、CostAwareRouter、Streaming。每层职责清晰而门禁层是唯一一个「不信任下游」的层。这篇会按可跟做的顺序走先讲清楚 ToolGuard 的拦截模型和它跟 prompt 审查的本质区别再说明为什么需要 TaoToken 这样的统一 Key/API 通道来配合门禁做调用归因然后给出可直接复制的门禁规则配置JSON/TOML/settings 三件套接着用一次真实的预执行拦截验证动作证明它确实在工具调用前生效最后对照 401、local proxy failed、reading choices、OAuth 这几类真实报错做排查。全程围绕「verify before execute」这一条线不铺开讲无关的框架对比。2. TaoToken 前置统一 Key/API 通道与门禁的配合方式ToolGuard 要拦截的是「工具调用」但工具调用最终会落到模型 API 上——Agent 决定调什么工具、传什么参数这个决策过程本身要经过模型。所以门禁要真正闭环必须同时管住两件事工具执行前的权限校验以及模型请求的通道归因。TaoToken 在这里的角色就是后者它提供统一的 Key 和 API 通道让 Jig 里所有 Agent 的模型请求走同一个入口这样 ToolGuard 在做拦截决策时能拿到一致的调用上下文而不是每个 Agent 各自持有不同的 Key、日志散落在各处。先说清楚 TaoToken 是什么、能做什么、适合谁。它是一个模型 API 的统一接入通道官网在 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content API 端点是 https://taotoken.net/api 这个地址不加 UTM。适合的场景是你手上有多个 Agent 或多个模型供应商想用一套 Key 管理所有调用同时希望调用日志能按 Agent、按工具、按会话归因。对 Jig 这种把门禁放在控制面的框架来说统一通道意味着 ToolGuard 的拦截记录和模型调用记录能对上——哪次拦截对应哪次模型决策一目了然。前置准备分三步。第一步拿到 API Key。访问 https://taotoken.net/api-keys 创建注意这个 Key 是给 Jig 的 ModelRouter 用的不是给单个 Agent 用的。第二步确认你要用的模型 IDJig 的 BaseModelProvider 接口只有 chat 和 chat_stream 两个方法DeepSeekProvider 和 OpenAIProvider 都实现了这两个方法所以模型 ID 要跟 Provider 匹配。第三步把 Base URL 指向 https://taotoken.net/api 不要带任何路径后缀Jig 的 ModelRouter 会自己拼 /v1/chat/completions 这类端点。这里有个容易踩的坑很多人会把 Base URL 写成 https://taotoken.net/api/v1 结果请求变成 /api/v1/v1/chat/completions直接 404。正确的写法就是 https://taotoken.net/api 让框架自己补路径。另一个坑是 Key 的权限范围——如果你在 TaoToken 控制台给这个 Key 限制了模型白名单而 Jig 的 CostAwareRouter 又试图路由到一个不在白名单里的模型会返回 403 而不是 401排查时容易误判成 Key 失效。为什么门禁需要统一通道举个具体例子。Jig 的 ToolGuard 白名单是按角色配的比如 pm 角色只能调 Read 和 Grepcoding 角色能调 Write 和 Bash。当 pm 角色的 Agent 试图调 Bash 时ToolGuard 会在工具执行前阻断。但如果模型请求走的是各自独立的 Key你事后想复盘「这个 pm Agent 当时为什么想调 Bash」就得去翻好几个不同的日志源。走 TaoToken 统一通道后模型请求和工具拦截记录都带同一个会话标识复盘时直接按 session_id 串起来就行。还有一层配合是成本归因。Jig 的 CostAwareRouter 会根据成本选择模型而 TaoToken 的调用记录能告诉你每个 Agent、每个角色实际消耗了多少。当 ToolGuard 拦截掉一次高危调用后这次拦截本身不产生工具执行成本但模型决策那次请求是已经发生的——统一通道能让你清楚看到「拦截省下了什么」和「决策花了什么」这对评估门禁的实际收益很关键。需要强调的是TaoToken 在这里是合法的 API 接入通道不是任何形式的非法中转。它的作用是统一管理和归因不改变模型本身的调用语义。Jig 的 ToolGuard 拦截逻辑完全在本地代码里执行不依赖通道做安全判断——通道只负责把请求送达和记录安全决策始终在 Control Plane。配置时还要注意一点Jig 的 ModelRouter 支持多 Provider你可以让 DeepSeekProvider 走 TaoToken 通道同时保留一个直连的 OpenAIProvider 做对比测试。但生产环境建议全部走统一通道否则 ToolGuard 的拦截上下文会出现缺口。具体做法是在 Jig 的配置里把 base_url 统一设成 https://taotoken.net/api 然后按 Provider 类型填对应的 model ID。3. 可复制配置门禁规则与模型通道三件套这一节给出可直接复制的配置片段分三部分ToolGuard 门禁规则JSON、Jig 运行配置TOML、以及模型通道的 settings 片段。三者的路径和字段名保持一致复制后改 Key 和模型 ID 就能跑。先看 ToolGuard 门禁规则。Jig 的 ToolGuard 接口从 v0.1 定型后没改过核心是 WHITELIST、DENYLIST 和 check 方法。实际使用时规则以 JSON 形式加载放在项目根目录的 config/toolguard.json { version: 0.6.0, default_policy: deny, roles: { pm: { allow: [Read, Grep, Search], deny: [Write, Bash, Edit] }, coding: { allow: [Read, Grep, Write, Edit, Bash], deny: [Bash(rm -rf /), Bash(curl * | sh)] }, security: { allow: [Read, Grep, Search, Bash(scan *)], deny: [Write, Edit] } }, global_denylist: [ Bash(rm -rf /), Bash(rm -rf ~), Bash(:(){ :|: };:), Write(/etc/passwd), Write(~/.ssh/authorized_keys) ], risk_mode: { enabled: true, high_risk_tools: [Bash, Write, Edit], require_confirmation: false, audit_log: logs/toolguard_audit.jsonl } }几个关键字段说明。default_policy 设成 deny 表示「未明确允许的一律拒绝」这是预执行门禁的核心——白名单思维不是黑名单思维。roles 里每个角色有自己的 allow 和 denydeny 优先级高于 allow所以 coding 角色虽然允许 Bash但 Bash(rm -rf /) 会被 global_denylist 拦下。risk_mode 里的 audit_log 会把每次拦截写进 JSONL方便和 TaoToken 的调用记录对齐。注意 DENYLIST 的写法支持参数级匹配Bash(rm -rf /) 这种形式会匹配命令和参数组合不是简单匹配工具名。这是 ToolGuard 比 prompt 审查强的地方——prompt 只能写「不要删根目录」模型可能理解成「不要删 / 但可以删 /*」而代码级匹配是精确的。再看 Jig 运行配置放在项目根目录的 jig.toml [agent] skill_dir skills default_role pm max_loop_iterations 10 [orchestration] sop pm-spec-coding-acceptance checkpoint_enabled true checkpoint_dir .jig/checkpoints graph_enabled true [orchestration.loop_engine] convergence_threshold 0.85 convergence_window 3 [memory] cache_size 50 partition_window 7d embedding_enabled true sqlite_path .jig/memory.db [circuit_breaker] failure_threshold 3 timeout_seconds 60 half_open_max_calls 1 [model] provider deepseek base_url https://taotoken.net/api api_key_env TAOTOKEN_API_KEY model_id deepseek-chat stream true [model.cost_router] enabled true prefer_low_cost true fallback_model deepseek-chat [toolguard] config_path config/toolguard.json enforce_before_execute true这里 model 段的 base_url 就是 TaoToken 的 API 地址api_key_env 指向环境变量 TAOTOKEN_API_KEY避免把 Key 写进配置文件。model_id 填你实际要用的模型stream 开启 SSE 流式。cost_router 的 fallback_model 要跟 model_id 一致否则路由失败时会报模型不存在。最后是模型通道的 settings 片段。如果你用 Claude Code 或 Codex 这类外部 Agent需要单独配它们的 settings让它们也走 TaoToken 通道这样 ToolGuard 的 Meta-Harness 才能统一管控。以 Claude Code 的 settings.json 为例路径在 ~/.claude/settings.json { env: { ANTHROPIC_BASE_URL: https://taotoken.net/api, ANTHROPIC_API_KEY: sk-your-taotoken-key, ANTHROPIC_MODEL: claude-sonnet-4-20250514 }, permissions: { allow: [Read, Grep], deny: [Bash(rm -rf /)] } }Codex 的 auth.json 路径在 ~/.codex/auth.json 写法类似{ base_url: https://taotoken.net/api, api_key: sk-your-taotoken-key, model: gpt-4o }三件套的核心是 Base URL、Key、Model ID 三者必须一致对应。Base URL 统一是 https://taotoken.net/api Key 从 https://taotoken.net/api-keys 拿Model ID 按你实际用的模型填。任何一处不一致都会导致 401 或模型不存在。配置完成后用一条命令验证加载是否成功python -c from jig import Jig; j Jig.from_config(jig.toml); print(j.toolguard.roles.keys()); print(j.model.base_url)预期输出是 dict_keys([pm, coding, security]) 和 https://taotoken.net/api 。如果第二行打印出别的地址说明 TOML 里的 base_url 没生效检查是不是被环境变量覆盖了。4. 验证请求一次预执行拦截的完整过程配置就绪后最关键的一步是验证 ToolGuard 真的在工具执行前拦截而不是执行后才报错。这一节用一个最小可复现的例子走完整过程让 pm 角色的 Agent 尝试调用 Bash观察拦截发生在哪一层。先准备一个 Skill 定义放在 skills/pm-agent/SKILL.md --- name: pm-agent description: 产品经理 Agent负责需求分析和文档整理 model: deepseek-chat role: pm tools: [Read, Grep, Search] --- 你是一个产品经理 Agent。你的职责是分析需求、整理文档、检索信息。 你只能使用 Read、Grep、Search 三个工具。不要尝试执行任何命令。注意 frontmatter 里 role 是 pmtools 只列了三个只读工具。但模型不一定听话——它可能在某个推理步骤里决定调 Bash 来「查看目录结构」。这正是要验证的场景。写一个测试脚本 verify_toolguard.py from jig import Jig from jig.toolguard import ToolGuard jig Jig.from_config(jig.toml) agent jig.create_agent(pm-agent) # 模拟模型决定调用 Bash tool_call { role: pm, tool: Bash, args: {command: ls -la /} } result ToolGuard.check( roletool_call[role], tooltool_call[tool], argstool_call[args] ) print(f拦截结果: {result.allowed}) print(f拦截原因: {result.reason}) print(f审计记录: {result.audit_id})运行export TAOTOKEN_API_KEYsk-your-taotoken-key python verify_toolguard.py预期输出拦截结果: False 拦截原因: role pm is not allowed to call tool Bash (deny list match) 审计记录: tg-20260726-a3f9c2关键点在于这个拦截发生在工具执行之前。ToolGuard.check 是纯代码判断不经过模型不产生任何工具执行副作用。如果换成 prompt 审查流程会是「模型先调 Bash → 执行 → 事后发现不对」而这里是「模型想调 Bash → 代码判断 → 拒绝 → 模型收到拒绝结果」。再验证一次参数级拦截。把 tool_call 改成 coding 角色调 Bash参数是危险命令tool_call { role: coding, tool: Bash, args: {command: rm -rf /} } result ToolGuard.check( roletool_call[role], tooltool_call[tool], argstool_call[args] ) print(f拦截结果: {result.allowed}) print(f拦截原因: {result.reason})预期输出拦截结果: False 拦截原因: global denylist match: Bash(rm -rf /)coding 角色本身允许 Bash但 global_denylist 里的参数级规则把它拦下了。这说明门禁是两层角色白名单 全局黑名单deny 优先。现在把拦截记录和 TaoToken 的调用记录对齐。查看审计日志cat logs/toolguard_audit.jsonl | tail -2输出类似{audit_id: tg-20260726-a3f9c2, role: pm, tool: Bash, allowed: false, reason: role deny list match, session_id: sess-8f2a, timestamp: 2026-07-26T10:23:41Z} {audit_id: tg-20260726-b7e1d4, role: coding, tool: Bash, allowed: false, reason: global denylist match, session_id: sess-8f2a, timestamp: 2026-07-26T10:23:42Z}两条记录都带 session_id。去 TaoToken 控制台的调用记录里按这个 session_id 查能看到对应的模型请求——模型在哪个推理步骤决定调 Bash、当时的上下文是什么。这就是统一通道的价值拦截记录和模型决策记录能串起来。最后验证一次「放行」的情况确认门禁不是无差别拒绝tool_call { role: pm, tool: Read, args: {path: docs/requirements.md} } result ToolGuard.check( roletool_call[role], tooltool_call[tool], argstool_call[args] ) print(f拦截结果: {result.allowed})预期输出 拦截结果: True 。pm 角色调 Read 在白名单里放行。整个验证过程的核心结论ToolGuard 的拦截是预执行的、代码级的、可审计的。它不依赖模型是否听话也不依赖 prompt 写得够不够严厉。模型可以「想」调任何工具但能不能「执行」由 Control Plane 决定。5. 常见错排查401、local proxy failed、reading choices、OAuth这一节对照四类真实报错给出定位思路和修复动作。这些报错在 Jig TaoToken 的组合里出现频率最高且容易误判。第一类401 Unauthorized。报错长这样jig.model.errors.AuthError: 401 Unauthorized {error: {message: Invalid API key, type: invalid_request_error}}定位顺序先确认 TAOTOKEN_API_KEY 环境变量是否设置且非空用 echo $TAOTOKEN_API_KEY 检查。再确认 Key 是否在 https://taotoken.net/api-keys 有效有没有被删除或过期。然后确认 jig.toml 里 api_key_env 的值和实际环境变量名一致——常见错误是配置里写 TAOTOKEN_API_KEY环境里设的是 TAOTOKEN_KEY。如果 Key 和环境变量都对检查 Base URL。401 有时是 Base URL 写错导致请求打到了别的端点。正确写法是 https://taotoken.net/api 不带 /v1。写成 https://taotoken.net/api/v1 会导致路径重复某些情况下返回 401 而不是 404。还有一种 401 是 Key 权限范围问题。如果 Key 在控制台限制了模型白名单而请求的 model_id 不在白名单里会返回 401 或 403。去控制台确认 Key 的模型权限包含你要用的 model_id。第二类local proxy failed。报错长这样jig.model.errors.TransportError: local proxy failed: connection refused这个报错通常出现在你本地配了某个代理端口但代理没启动。Jig 的 ModelRouter 会读取 HTTP_PROXY / HTTPS_PROXY 环境变量。检查echo $HTTP_PROXY echo $HTTPS_PROXY如果输出了本地地址比如 http://127.0.0.1:7890而那个端口没有服务在监听就会 connection refused。修复方式是 unset 这两个变量让请求直连 TaoToken 通道unset HTTP_PROXY unset HTTPS_PROXY python verify_toolguard.py注意这里说的代理是本地网络配置层面的不是任何形式的网络绕过工具。TaoToken 通道本身是直连的不需要额外代理。如果你确实需要代理才能访问外网那是你的网络环境问题跟 Jig 和 TaoToken 无关。第三类reading choices。报错长这样jig.model.errors.ResponseParseError: error reading choices: unexpected end of JSON input这个报错说明请求发出去了但响应体不完整或格式不对。常见原因有三个。一是 stream 模式下的 SSE 分片解析问题——如果 jig.toml 里 stream true但某个 Provider 的 chat_stream 实现没正确处理分片会读到半截 JSON。临时把 stream 设成 false 验证[model] stream false如果关掉流式就正常说明是流式解析的 bug检查 Provider 实现里的 buffer 处理逻辑。二是响应被截断。如果模型返回的内容很长而客户端读取超时会读到不完整的 JSON。调大超时[model] timeout_seconds 120三是 model_id 写错请求打到了不存在的模型返回的错误体不是标准的 choices 格式。确认 model_id 和 Provider 匹配——deepseek-chat 配 DeepSeekProvidergpt-4o 配 OpenAIProvider。第四类OAuth 相关报错。报错长这样jig.model.errors.AuthError: OAuth token expired, please re-authenticate这个报错出现在你用 OAuth 方式认证的场景。Jig 本身用 API Key 认证但如果你在 Claude Code 或 Codex 的 settings 里配了 OAuth而 token 过期了会报这个。修复方式是重新走一遍认证流程或者改用 API Key 方式。以 Claude Code 为例如果 settings.json 里配的是 OAuth改成 API Key{ env: { ANTHROPIC_BASE_URL: https://taotoken.net/api, ANTHROPIC_API_KEY: sk-your-taotoken-key, ANTHROPIC_MODEL: claude-sonnet-4-20250514 } }Codex 的 auth.json 同理把 OAuth 字段换成 api_key 字段。注意 Base URL、Key、Model ID 三件套要一致任何一处用 OAuth 残留都会导致认证混乱。排查通用原则先看报错类型401 是认证TransportError 是网络ResponseParseError 是解析OAuth 是认证方式再按「环境变量 → 配置文件 → 控制台权限 → 网络环境」的顺序逐层确认。大部分问题出在环境变量和配置文件不一致上。如果四类都排查完还是不通用最小请求验证通道本身curl -X POST https://taotoken.net/api/v1/chat/completions \ -H Authorization: Bearer $TAOTOKEN_API_KEY \ -H Content-Type: application/json \ -d {model: deepseek-chat, messages: [{role: user, content: ping}]}如果这条 curl 通说明通道没问题问题在 Jig 配置如果不通说明 Key 或通道有问题去 https://taotoken.net/api-keys 重新确认。6. 把门禁接进你的 Agent 管道走到这里你已经有了可复制的门禁规则、可运行的验证脚本、和四类报错的排查路径。接下来要做的是把 ToolGuard 接进你现有的 Agent 管道而不是停在单次验证。接入的第一步是确定你的管道里哪些节点需要门禁。Jig 的 SOP 管道是 PM → Spec → Coding → Acceptance 四节点每个节点的角色不同门禁规则也不同。PM 和 Spec 是只读角色白名单里只放 Read、Grep、SearchCoding 需要写和执行白名单放宽但全局黑名单收紧Acceptance 是验收角色通常只需要 Read 和 Grep。按这个思路在 config/toolguard.json 里为每个节点配一个角色而不是所有节点共用一个角色。第二步是把 enforce_before_execute 设成 true并且确认它在所有执行路径上都生效。Jig 的 ToolGuard 默认在工具调用前检查但如果你自己写了工具执行逻辑绕过了 ToolGuard.check门禁就形同虚设。检查方式是搜代码里所有工具执行点确认每个点前面都有 ToolGuard.check 调用。Jig 内置的工具执行器已经做了这件事但自定义工具需要自己加。第三步是把审计日志和 TaoToken 的调用记录做关联。audit_log 里的 session_id 和 TaoToken 调用记录里的会话标识要对齐这样复盘时能串起来。如果 session_id 对不上检查 Jig 的会话管理是不是每个 Agent 独立生成 session_id——统一通道下应该用同一个 session_id 贯穿整个管道。第四步是定期看拦截记录调整规则。如果某个角色的拦截率异常高可能是白名单太严也可能是模型在尝试不该做的事。前者放宽规则后者说明门禁在起作用。Jig 的 risk_mode 里可以开 require_confirmation让高危工具调用需要人工确认但生产环境建议先用 audit_log 观察一段时间再决定要不要开。对于外部 AgentClaude Code、Codex用 Jig 的 Meta-Harness 做统一管控。Meta-Harness 是 v0.6 之后的方向核心思路是用 Jig 的 ToolGuard 管控外部 Agent 的工具调用。配置方式是在外部 Agent 的 settings 里把 Base URL 指向 TaoToken 通道然后在 Jig 侧配一个对应的角色和门禁规则。这样外部 Agent 的模型请求走统一通道工具调用经过 ToolGuard 检查拦截记录和内部 Agent 的记录格式一致。一个实用技巧把 ToolGuard 的拦截结果反馈给模型。当模型调用的工具被拦截时不要只是静默拒绝而是把拒绝原因作为工具调用结果返回给模型。这样模型能知道「这个工具不能用」在后续推理里调整策略而不是反复尝试同一个被拒的工具。Jig 的 ToolGuard.check 返回的 result.reason 可以直接作为工具结果回传。最后门禁规则不是一次配好就不管的。随着 Agent 能力变化和业务需求调整白名单和黑名单都要跟着改。建议把 config/toolguard.json 纳入版本管理每次改动都记录原因。Jig 的 124 个测试里有一部分就是门禁规则的回归测试你可以参考它的测试写法给自己的规则加测试。如果你还没开始从最小配置起步一个 pm 角色、一个 coding 角色、一条全局黑名单跑通验证脚本再逐步加规则。TaoToken 的 Key 在 https://taotoken.net/api-keys 创建接入文档在 https://taotoken.net/doc 模型对话调试在 https://taotoken.net/chat 长期编码和 Agent 场景可以看 https://taotoken.net/coding-plan 。先把通道打通再把门禁接上顺序不要反。
锦
锦皓数字建站
深耕本土企业品牌数字化升级,专注原创端正雅致商务官网,从视觉设计到稳定运维全程保驾护航。