TauriTavern/docs/Agent/ProfilesAndPreset.md

11 KiB
Raw Blame History

Agent Profiles, Preset Schema, and Plan Policy

本文档定义 Agent Profile、Preset Agent 扩展字段、ContextFrame、Prompt Component 与 Plan Policy。

SillyTavern 的核心优势之一是 prompt/preset 的创作者自由。Agent Mode 必须继承这一点,但自由度要进入可维护的 runtime policy,而不是散落在字符串 prompt 中。

当前已落地字段以 ResolvedAgentProfile 为准:preset.mode 支持 currentPromptSnapshot / ref / none,preset.ref 当前用于独立 OpenAI/chat-completion preset 组装;model.mode 支持 currentPromptSnapshot / connectionRef,connectionRef + modelId 会通过 LLM Connection 解耦 preset source/model。完整生产链路见 PromptAssembly.md。

1. Agent Profile

Agent Profile 是:

Preset + Model/API + Context Policy + Tool Policy + Plan Policy + Output Policy

不是单纯的模型选择。

建议领域模型:

AgentProfile {
    id,
    display_name,
    preset_ref,
    model_ref,
    prompt_policy,
    visible_resource_policy,
    tool_policy,
    plan_policy,
    summary_policy,
    switch_policy,
    output_policy,
    budget_policy,
}

1.1 Profile 来源

Profile 可以来自:

  • 用户手动创建。
  • Preset 内嵌 agent schema。
  • 角色卡推荐配置。
  • 扩展提供的 profile template。

最终运行时必须 resolve 为一个完整、可检查的 ResolvedAgentProfile。

1.2 Profile Resolution

解析顺序建议:

Built-in defaults
  < Preset agent schema
  < Character agent schema
  < User profile override
  < Per-run override

冲突规则必须明确:

  • deny 优先于 allow。
  • plan node policy 优先于 profile global policy。
  • user explicit deny 优先级最高。
  • missing required field fail-fast。

1.3 Agent System Prompt Ownership

instructions.agentSystemPrompt 只属于 Agent Profile:null 使用 resolved profile 默认值,非空字符串完整替换默认值。

Preset / PromptManager 中的 agentSystemPrompt 不是内容源,而是 Agent Mode 的组装位置、enabled 状态与 role 契约。前端必须先解析 Agent Profile,再在该 PromptManager index materialize 真实 Agent system prompt;Rust runtime 只消费组装后的最终 messages,并 fail-fast 拒绝内部 marker 泄漏。Legacy Generation 必须移除 Agent-only 组件,不能看到 agentSystemPrompt 或 Agent system prompt 内容。

2. Preset Agent Schema

第一版可以使用 JSON-compatible schema,不必立刻引入 YAML。

示例:

{
  "agent": {
    "enabled": true,
    "profiles": [
      {
        "id": "writer",
        "displayName": "Writer",
        "model": { "source": "openai", "model": "..." },
        "context": {
          "historyBudgetTokens": 12000,
          "workspaceFileBudgetTokens": 6000,
          "toolResultBudgetTokens": 2000,
          "include": [
            "chat.history",
            "world.activated",
            "character.instructions",
            "workspace.output",
            "agent.plan",
            "agent.tool_results"
          ],
          "exclude": ["workspace.scratch.private"]
        },
        "tools": {
          "allow": [
            "workspace.read_file",
            "workspace.apply_patch",
            "chat.search",
            "skill.read"
          ],
          "deny": ["shell.*"],
          "requireApproval": ["mcp.*", "workspace.commit"]
        },
        "output": {
          "artifacts": [
            { "id": "main", "path": "output/main.md", "kind": "body", "target": "message_body" },
            { "id": "status", "path": "output/status.md", "kind": "status", "target": { "message_extra": "status_bar" } }
          ]
        }
      }
    ],
    "defaultProfile": "writer"
  }
}

第一期可以只支持:

  • enabled
  • defaultProfile
  • profiles[].id
  • profiles[].model
  • profiles[].context
  • profiles[].tools
  • profiles[].output.artifacts

更复杂的 plan/profile switch 属于后续 profile routing 工作。

3. ContextFrame

ContextFrame 是 Agent Mode 的 prompt 组织真相。

它应该表达 typed components:

SystemInstruction
ChatHistory
WorldInfo
CharacterCard
UserProfile
PresetGuide
WorkspaceTree
WorkspaceFile
ToolDefinitions
ToolResults
Plan
DiffSummary
Skill

每个 component 至少包含:

id
kind
source
visibility
tokenBudget
priority
contentRef or inlineContent
metadata

ContextFrame 不是 provider payload。Provider adapter 只消费编译后的 ModelRequest。

4. Prompt 宏

创作者看到的是宏:

{{agent.plan}}
{{agent.workspace.tree}}
{{agent.file "output/main.md"}}
{{agent.file "scratch/notes.md" budget=800}}
{{agent.tools.available}}
{{agent.tool_results mode="summary" budget=1200}}
{{agent.diff.latest}}
{{agent.skill "long-form-romance"}}

宏展开必须生成 typed component 或 component reference,而不是简单字符串替换。

原因:

  • provider adapter 可以决定 system/user/tool/resource 位置。
  • component 可以独立预算和摘要。
  • hidden/private resource 可以被 policy 拒绝。
  • prompt cache 可以按 component 做。
  • tool result 可以与 chat history 平级。

5. Context Budget

Budget 必须可组合:

totalContextBudget
historyBudgetTokens
workspaceFileBudgetTokens
toolResultBudgetTokens
skillBudgetTokens
worldInfoBudgetTokens
summaryBudgetTokens

超预算策略:

truncate
summarize
drop_optional
fail

默认建议:

  • required component 超预算:fail-fast。
  • optional component 超预算:按 priority drop,并写 context_component_skipped event。
  • tool result 超预算:优先摘要。
  • chat history 超预算:使用 windowed read/search + summary。

6. Tool Policy

Tool policy 应能表达:

{
  "allow": ["workspace.*", "chat.search"],
  "deny": ["shell.*"],
  "requireApproval": ["mcp.*"],
  "maxCallsPerRun": 20,
  "maxCallsPerTool": {
    "chat.search": 5
  }
}

解析规则:

  1. user deny 最高。
  2. plan node deny/allow 覆盖 profile global allow。
  3. deny 优先 allow。
  4. requireApproval 不等于 deny。
  5. 未显式 allow 的工具默认不可见,除非 profile 选择 permissive mode。

建议默认 conservative mode:不在 allow list 的工具不可见。

7. Visible Resource Policy

资源可见性要与工具独立。

示例:

{
  "include": [
    "chat.history.tail",
    "world.activated",
    "workspace.output",
    "agent.plan"
  ],
  "exclude": [
    "workspace.scratch.private",
    "user.secrets",
    "mcp.resource.private"
  ]
}

Agent 不能通过 workspace.read_file 绕过 hidden resource policy。

8. Plan Policy

Plan Mode 有三种:

free
strict
hybrid

8.1 Free Plan

Agent 可以创建和修改计划。

运行时仍必须要求:

  • 先产出 plan。
  • 每个阶段结束 checkpoint。
  • 完成前检查 artifact manifest。
  • 不能突破全局 tool/resource/budget policy。

8.2 Strict Plan

Preset/创作者提供固定节点。

Agent 不能:

  • 改节点顺序。
  • 跳过 locked 节点。
  • 使用节点外工具。
  • 切换到节点外 profile。
  • 写节点外 expected artifact,除非 output policy 允许。

违反必须 fail-fast 或进入 approval,不能静默继续。

8.3 Hybrid Plan

部分 locked,部分 free。

推荐作为高级默认模式:

outline locked
write free
polish locked

它同时保留创作者控制和模型发挥空间。

9. Plan Node

建议模型:

PlanNode {
    id,
    title,
    locked,
    profile_id,
    allowed_tools,
    visible_files,
    max_rounds,
    context_budget,
    expected_artifacts,
    approval_required,
}

Plan node 开始/完成必须写 journal:

plan_node_started
plan_node_completed

10. Profile Switch

Profile switch 可以来自:

  • plan node 指定。
  • model request。
  • runtime policy。
  • user override。

必须检查:

  • current plan node 是否允许 switch。
  • target profile 是否存在。
  • target profile 的 tool/resource/model policy 是否满足平台限制。
  • switch 次数是否超 budget。

结果必须写 journal:

profile_switch_requested
profile_switched
profile_switch_denied

11. 创作者自由与安全边界

创作者可以控制:

  • Agent 可见哪些内容。
  • Agent 可用哪些工具。
  • 输出有哪些 artifact。
  • 哪些阶段严格,哪些阶段自由。
  • 是否需要审批。
  • token/tool/context budget。
  • profile/model 切换策略。

创作者不能控制:

  • workspace root 之外的文件访问。
  • MCP stdio command。
  • 平台 policy 禁用的 provider/source/endpoint override。
  • 用户显式 deny 的工具。
  • journal 是否记录副作用。
  • commit 是否绕过保存契约。

12. MVP Profile

当前状态(2026-05-04):Phase 3 Profile 基线已实现 profile resolution,但尚未实现 profile routing、Plan Mode runtime、provider/model switch 或 ContextFrame 预算。profileId 会驱动 tools、Skill、workspace roots、output artifact、tool budget、max rounds、model retry 与 model-facing prompt/tool descriptions。preset.ref 目前只做校验/记录,不隐式切换 model。

当前最小 built-in profile 是 default-writer,缺省 profileId 时使用它:

{
  "id": "default-writer",
  "preset": {
    "mode": "currentPromptSnapshot",
    "required": false
  },
  "model": {
    "mode": "currentPromptSnapshot"
  },
  "run": {
    "presentation": "foreground",
    "modelRetry": {
      "maxRetries": 3,
      "intervalMs": 3000
    }
  },
  "instructions": {
    "agentSystemPrompt": null
  },
  "tools": {
    "allow": [
      "workspace.list_files",
      "workspace.search_files",
      "workspace.read_file",
      "workspace.write_file",
      "workspace.apply_patch",
      "workspace.finish",
      "chat.search",
      "chat.read_messages",
      "worldinfo.read_activated",
      "skill.list",
      "skill.search",
      "skill.read"
    ],
    "deny": [],
    "toolDescriptions": {},
    "maxRounds": 80,
    "maxCallsPerRun": 80
  },
  "skills": {
    "visible": ["*"],
    "deny": [],
    "maxReadCharsPerCall": 20000,
    "maxReadCharsPerRun": 80000
  },
  "workspace": {
    "visibleRoots": ["output", "scratch", "plan", "summaries", "persist"],
    "writableRoots": ["output", "scratch", "plan", "summaries", "persist"]
  },
  "plan": {
    "mode": "none",
    "beta": true,
    "nodes": []
  },
  "output": {
    "artifacts": [
      { "id": "main", "path": "output/main.md", "kind": "markdown", "target": "messageBody", "required": true }
    ]
  }
}

Profile 文件存储在 _tauritavern/agent-profiles/profiles/<id>.json。每个 run 会固化 input/resolved_profile.json,运行时只消费 resolved profile。后续 profile routing 应在此基础上扩展,而不是绕过现有 resolver、registry 与 dispatcher。