45 KiB
window.__TAURITAVERN__.api.agent — Agent API
本文档是 Agent Host ABI 当前参考。Run 控制入口属于 Public Contract;明确标注为 Project Contract 的 UI/诊断投影可以随 Agent 实验能力演进,不等同于 Rust 内部 service/repository。
状态:当前已实现 canonical model IR、provider native metadata 保真、provider_state continuation、上下文只读工具、Skill tools、workspace 读改工具循环、可选工具参数 live projection、前端 dryRun adapter、Agent run history listing、Agent run retention facade / dry-run plan preview / manual apply prune / backend auto prune,以及 Agent Profile 独立 preset / 独立 model 的 Frontend PromptAssemblyBroker 链路。Agent System 扩展开关开启时,普通发送、/trigger、regenerate 与 overswipe 新候选生成会通过 Legacy Generate 兼容桥启动 Agent;普通切换已有 swipe 候选不启动 Agent。本文只记录当前已落地 Host ABI。
provider_state 是 Rust 后端内部 continuation contract,不是 Host ABI。前端/扩展不应读写 _tauritavern_provider_state;需要诊断时通过 run events、modelResponsePath 与 LLM API log 观察。
模型回合详情必须通过 readModelTurn() 读取后端投影 DTO;前端不解析 model-responses/ raw JSON。
1. 入口
await (window.__TAURITAVERN__?.ready ?? window.__TAURITAVERN_MAIN_READY__);
const agent = window.__TAURITAVERN__.api.agent;
Agent API 必须挂在 window.__TAURITAVERN__.api.agent。不要新增散落全局。
2. 当前方法概览
type TauriTavernAgentApi = {
startRunFromLegacyGenerate(input?: AgentStartRunFromLegacyGenerateInput): Promise<AgentRunHandle>;
startRunWithPromptSnapshot(input: AgentStartRunWithPromptSnapshotInput): Promise<AgentRunHandle>;
subscribe(runId: string, handler: (event: AgentRunEvent) => void, options?: AgentSubscribeOptions): TauriTavernHostUnsubscribe;
subscribeLiveProjection(runId: string, handler: (update: AgentRunLiveUpdate) => void, options?: AgentLiveSubscribeOptions): TauriTavernHostUnsubscribe;
settleChatPresentation(handle: AgentRunHandle): Promise<void>; // Host generation adapter lifecycle
cancel(runId: string): Promise<AgentRunHandle>;
submitGuidance(input: AgentSubmitGuidanceInput): Promise<AgentSubmitGuidanceResult>;
readEvents(input: AgentReadEventsInput): Promise<AgentReadEventsResult>;
readWorkspaceFile(input: AgentReadWorkspaceFileInput): Promise<AgentWorkspaceFile>;
readModelTurn(input: AgentReadModelTurnInput): Promise<AgentModelTurn>;
readTaskDetail(input: AgentReadTaskDetailInput): Promise<AgentTaskDetail>;
copyChatPersistentStates(input: AgentCopyChatPersistentStatesInput): Promise<void>;
pruneChatPersistentStates(input: AgentPruneChatPersistentStatesInput): Promise<AgentPruneChatPersistentStatesResult>;
retention: {
readSettings(): Promise<AgentRunRetentionSettings>;
updateSettings(input: Partial<AgentRunRetentionSettings>): Promise<AgentRunRetentionSettings>;
planPrune(input?: {
retention?: AgentRunPruneRetention | AgentRunRetentionSettings;
detailLimit?: number;
}): Promise<AgentRunPrunePlan>;
applyPrune(input?: {
retention?: AgentRunPruneRetention | AgentRunRetentionSettings;
detailLimit?: number;
}): Promise<AgentRunPruneApplyResult>;
};
profiles: {
list(): Promise<AgentListProfilesResult>;
load(input: string | { profileId: string }): Promise<{ profile: AgentProfileDefinition | null }>;
diagnose(input: string | { profileId: string }): Promise<AgentProfileHealth>;
resolveSystemPrompt(input?: string | { profileId?: string | null }): Promise<{ agentSystemPrompt: string }>;
repairFile(input: { profileId: string; action: 'delete' | 'normalizeIdentity' }): Promise<void>;
retargetPresetRefs(input: {
from: { apiId: string; name: string };
to: { apiId: string; name: string };
}): Promise<{ updated: number; profileIds: string[] }>;
save(input: AgentProfileDefinition | { profile: AgentProfileDefinition }): Promise<void>;
delete(input: string | { profileId: string }): Promise<void>;
};
promptAssembly: {
prepare(input: AgentPromptAssemblyPrepareInput): Promise<AgentPromptAssemblyPrepareResult>;
buildSnapshot(input: AgentPromptAssemblyBrokerRequest): Promise<AgentPromptAssemblyBuildResult>;
};
tools: {
list(): Promise<{ tools: AgentToolCatalogItem[] }>;
};
approveToolCall(): never;
listRuns(input?: {
chatRef?: AgentChatRef;
stableChatId?: string;
statuses?: AgentRunStatus[];
before?: { createdAt: string; runId: string };
limit?: number;
}): Promise<{
runs: AgentRunSummary[];
nextCursor?: { createdAt: string; runId: string };
}>;
};
subscribe() 返回的 unsubscribe 必须幂等。
当前没有公共 startRun() alias。启动职责必须一眼可见:
startRunFromLegacyGenerate():从当前 Legacy Generate dryRun 兼容桥启动。startRunWithPromptSnapshot():调用方已经持有 prompt snapshot 时启动。
approveToolCall() 已预留名称,但当前实现会显式 throw,避免静默降级。
3. startRunFromLegacyGenerate
type AgentStartRunFromLegacyGenerateInput = {
chatRef?: AgentChatRef;
stableChatId?: string;
generationType?: 'normal' | 'regenerate' | 'swipe' | 'continue' | 'quiet' | 'impersonate';
generateOptions?: unknown;
profileId?: string;
generationIntent?: AgentGenerationIntent;
presentation?: 'foreground' | 'background';
options?: {
presentation?: 'foreground' | 'background';
stream?: boolean;
};
};
startRunFromLegacyGenerate() 是当前前端兼容桥:它使用 Legacy Generate(..., dryRun = true) 捕获当前 SillyTavern prompt 输入语义与 FrozenRunInputSnapshot。若 Profile 使用独立 preset,则先通过 promptAssembly.prepare() / buildSnapshot() 复用真实 PromptManager 组装;否则物化当前 prompt snapshot,再调用 startRunWithPromptSnapshot()。
要求:
- 只用于当前 active chat。
- 当前只支持
main_api = openai的 chat-completion 路径。 - 必须禁用 Legacy ToolManager tools;Agent tools 只能由 Rust runtime 注册。
worldInfoActivation必须来自本次 dryRun 的最终WORLDINFO_SCAN_DONE,不能读取全局 last activation 当作 run 真相。stream是可选的 run-wide override;省略时每个 invocation 使用自己的 resolved Profilerun.stream(缺失字段默认false),显式值覆盖整次 run。true只支持后端已接入的 OpenAI Chat-compatible/chat/completions与 Custom OpenAI Responses/responsesroute,不支持时明确失败。presentation可显式覆盖 profile 默认前台/后台语义。- dryRun 没有产出 messages、已有 tool turns、已有 external tools 都必须 reject,不回退 Legacy Generate。
- 入口路由到 Agent 后,profile、provider、group chat、context policy 或 Host API 错误必须 reject 并显式呈现;不得静默降级为 Legacy Generate。
/trigger作为生成入口遵守同一规则。
注意:Generate(..., dryRun = true) 不返回 payload。它只 emit GENERATE_AFTER_DATA,然后 resolve undefined。调用方不应写 const payload = await Generate(..., true);捕获逻辑由 startRunFromLegacyGenerate() 内部 adapter 负责。
4. startRunWithPromptSnapshot
type AgentStartRunWithPromptSnapshotInput = {
chatRef: AgentChatRef;
stableChatId?: string;
generationType?: 'normal' | 'regenerate' | 'swipe' | 'continue' | 'quiet' | 'impersonate';
profileId?: string;
promptSnapshot: AgentPromptSnapshot;
frozenRunInputSnapshot?: unknown;
generationIntent?: AgentGenerationIntent;
workspaceMode?: 'new-run' | 'resume-run';
resumeRunId?: string;
presentation?: 'foreground' | 'background';
options?: {
presentation?: 'foreground' | 'background';
stream?: boolean;
};
};
type AgentPromptSnapshot = {
contextPolicy: unknown;
chatCompletionPayload: unknown;
worldInfoActivation?: {
timestampMs: number;
trigger: string;
entries: Array<{
world: string;
uid: string | number;
displayName: string;
constant: boolean;
content: string;
position?: 'before' | 'after' | 'an_top' | 'an_bottom' | 'depth' | 'em_top' | 'em_bottom' | 'outlet';
}>;
};
};
type AgentRunHandle = {
runId: string;
status: AgentRunStatus;
workspaceId: string;
stableChatId: string;
generationType: string;
};
type AgentRunSummary = {
runId: string;
workspaceId: string;
stableChatId: string;
chatRef: AgentChatRef;
generationType: string;
profileId?: string;
skillScopeRefs?: {
preset?: { apiId: string; name: string };
characterId?: string;
};
persistBaseStateId?: string;
inputMessageCount?: number;
presentation: 'foreground' | 'background';
status: AgentRunStatus;
createdAt: string;
updatedAt: string;
commitCount: number;
committedMessage?: {
commitId: string;
messageId: string;
messageIndex?: number;
committedAt: string;
};
terminalAt?: string;
};
AgentRunSummary.committedMessage.messageIndex 是 host commit 当时的零基消息索引快照,由 chat_commit_completed.messageId 派生;它不承诺当前聊天仍在该位置。旧 run 没有可解析 messageId 时该字段为空,前端不应显示楼层定位。summary projection 是可从 journal 重建的缓存,只在已经写入 terminal event 的终态 run 上复用/落盘。
身份语义:
stableChatId是聊天的长期稳定身份。workspaceId必须由kind + stableChatId派生,不得由可变的chatRef文件名直接决定。runId是一次 Agent 执行身份,每次 normal/regenerate/swipe/continue 都必须生成新的runId。generationType是启动时已校验的 SillyTavern generation intent;前端 retry 等宿主行为必须使用它或generation_intent_recorded事件,不得从 DOM 状态猜测。- 同一稳定聊天的多次 run 应共享同一个 chat workspace,但各自拥有独立 run workspace。
Public Host ABI 可以允许调用方省略 stableChatId,但 api.agent.startRunWithPromptSnapshot() 必须在调用 Rust command 前通过 api.chat.open(chatRef).stableId() 解析并校验。Rust command 不应自行读取 SillyTavern metadata。
当前要求提供 promptSnapshot.contextPolicy 与 promptSnapshot.chatCompletionPayload。promptSnapshot.worldInfoActivation 是可选字段,由 worldinfo_read_activated 读取;frozenRunInputSnapshot 用于审计与后续独立 prompt assembly 复用。长期目标是 generationIntent + ContextFrame,但当前 Rust runtime 不会只凭 generationIntent 组装上下文。
要求:
stableChatId进入 backend DTO 前必须非空;无法解析时 fail-fast。promptSnapshot.chatCompletionPayload必须包含 chat-completion payload object。- 如果调用方希望
worldinfo_read_activated返回非错误结果,必须在 prompt snapshot 中提供本次 run 的worldInfoActivation。 - 当前拒绝已有
tools、tool_choice、role: "tool"或已有tool_calls的外部 tool turns。 stream: true启用非权威工具参数 live projection;完整 provider final 仍进入与非流式相同的 Runtime、Journal、Gate 与工具执行路径。options.stream省略时,每个 root / child / handoff invocation 使用自己的 resolved Profilerun.stream;显式true/false是整次 run override。- live route 只支持后端已接入的 OpenAI Chat-compatible
/chat/completions与 Custom OpenAI Responses/responses;其它 source/endpoint 明确失败,不回退 buffered 请求。 workspaceMode/resumeRunId当前只是后续字段,不应作为当前行为依赖。- 参数无效必须 reject,不静默回退 Legacy Generate。
5. subscribe
type AgentSubscribeOptions = {
afterSeq?: number;
limit?: number;
intervalMs?: number;
onError?: (error: unknown) => void;
};
语义:
- 当前
subscribe()是前端 polling wrapper,底层调用readEvents()。 - 默认从
afterSeq = 0开始读取;调用方可以传入afterSeq。 - 返回 unsubscribe 函数,必须幂等。
- 底层 polling 细节和 Rust command 名不是 Public Contract。
5.1 subscribeLiveProjection
subscribeLiveProjection() 是 run-scoped、非持久化的工具参数投影,只用于观察当前生成中的 workspace.write_file / workspace.apply_patch。它与 durable subscribe() 相互独立。
type AgentRunLiveToolCall =
| {
toolId: 'builtin:workspace.write_file';
invocationId: string;
invocationExitPolicy: 'run_finish_allowed' | 'task_return_required';
toolCallIndex: number;
path: string;
content: string;
contentWords: number;
}
| {
toolId: 'builtin:workspace.apply_patch';
invocationId: string;
invocationExitPolicy: 'run_finish_allowed' | 'task_return_required';
toolCallIndex: number;
path: string;
oldString: string;
oldStringWords: number;
newString: string;
newStringWords: number;
};
type AgentRunLiveUpdate =
| { type: 'snapshot'; calls: AgentRunLiveToolCall[] }
| {
type: 'append';
invocationId: string;
toolCallIndex: number;
field: 'path' | 'content' | 'oldString' | 'newString';
text: string;
wordDelta: number;
}
| { type: 'replace'; call: AgentRunLiveToolCall }
| { type: 'remove'; invocationId: string; toolCallIndex: number };
type AgentLiveSubscribeOptions = {
onError?: (error: unknown) => void;
};
语义:
- 第一条 update 是 retained
snapshot;append只传新增文本,新 call 或 retry 使用replace。 snapshot/replace的字段词数由完整文本计算;append.wordDelta按本次 append 文本计算并由消费者累计。delta 可能切开一个字母数字词,因此 live 计数是近似值;工具完成后的 durable 文本指标仍按完整文本精确计算。tool_call_requested写入 Journal 后 live call 被remove;run 或 Channel 结束时订阅完成,错误调用onError。- unsubscribe 只幂等停止本地 handler。
- live update 不写 Journal、不能历史 replay,也不是 SillyTavern
STREAM_TOKEN_RECEIVED/TOOL_CALLS_*事件。 - Host 把前台 root / handoff
write_file.content写入真实 assistant message;Timeline 独立展示 live write/patch。apply_patch仍通过正式 auto-commit 改变聊天正文。 - live projection 不构成工具成功、commit、Agent metadata 或 persistent state;failure/cancel 保留已显示的 partial。
settleChatPresentation(handle)是 Legacy Generate adapter 使用的 Project lifecycle seam:它等待 terminal partial save 完成后再允许 generation 收尾,不是新的 Agent 状态或 commit API。
6. cancel
await agent.cancel(runId);
语义:
- 写
run_cancel_requested。 - 尽力取消当前模型请求或工具调用。
- Cancel 不是 failure。
- Cancel 后不能自动 commit。
- 返回最新
AgentRunHandle。
6.1 submitGuidance
await agent.submitGuidance({
runId,
text: "Prefer the quieter ending and keep the character's agency clear.",
clientGuidanceId: "optional-client-correlation-id",
});
type AgentSubmitGuidanceInput = {
runId: string;
text: string;
clientGuidanceId?: string;
};
type AgentSubmitGuidanceResult = {
runId: string;
guidanceId: string;
clientGuidanceId?: string;
status: 'queued';
preview: string;
chars: number;
words: number;
pendingCount: number;
};
语义:
submitGuidance()是 active AgentRun 的 run-scoped 输入通道,不是普通聊天发送,不写入 SillyTavern chat history,也不会启动新的 Generate。- Runtime 会先写
user_guidance_submitted,再将 guidance 放入当前 active run mailbox;下一次 root / handoff 前台 invocation 创建模型请求前,pending guidance 会合并为一条 canonicalrole=usermessage,并写user_guidance_applied。 - 已经发出的 provider request 不会被热修改。若当前模型调用正在进行,guidance 只影响后续模型请求边界。
cancel()、run finish、run failure / partial success 会关闭 mailbox;尚未应用的 guidance 写user_guidance_discarded。- 空文本、过长文本、非 active run、
finishing/cancelling/ terminal run、mailbox 已满都会 fail-fast,不做静默丢弃。
7. approveToolCall
当前未实现审批流程;approveToolCall() 会显式 throw。
type AgentApproveToolCallInput = {
runId: string;
callId: string;
approved: boolean;
reason?: string;
};
语义:
- 审批结果写 journal。
- 拒绝工具不等同 run failure;具体后续由 plan/profile policy 决定。
8. readEvents
type AgentReadEventsInput = {
runId: string;
afterSeq?: number;
beforeSeq?: number;
limit?: number;
invocationId?: string;
includeTimelineProjection?: boolean;
};
type AgentReadEventsResult = {
events: AgentRunEvent[];
timelineProjection?: AgentRunTimelineProjection;
};
type AgentRunTimelineProjection = {
foregroundInvocationIds: string[];
invocations: AgentRunTimelineInvocation[];
delegationEdges: AgentRunTimelineDelegationEdge[];
};
type AgentRunTimelineInvocation = {
invocationId: string;
parentInvocationId?: string;
profileId: string;
kind: 'root' | 'subagent' | 'handoff';
status: 'created' | 'running' | 'completed' | 'failed' | 'cancelled' | 'transferred';
exitPolicy: 'run_finish_allowed' | 'task_return_required';
createdAt: string;
updatedAt: string;
};
type AgentRunTimelineDelegationEdge = {
taskId: string;
sourceInvocationId: string;
targetInvocationId: string;
targetProfileId: string;
workspaceKey: string;
continuation: 'return_to_parent' | 'transfer_control';
status: 'queued' | 'running' | 'completed' | 'failed' | 'cancelled';
resultRef?: string;
error?: string;
createdAt: string;
updatedAt: string;
};
要求:
limit必须有上限。- 移动端 UI 不应一次读取完整巨大 journal;推荐先用
beforeSeq读取最新页,再在用户向上回看时继续用beforeSeq补拉更早事件,同时用afterSeq追新。 invocationId可选。传入时,后端先按 invocation 归属过滤事件流,再应用afterSeq/beforeSeq/limit;因此分页窗口表达的是该 invocation 自己的历史页,不是全局页的二次筛选。该能力用于 SubAgent / handoff 局部 timeline,避免移动端为查看一个子 Agent 搬运完整 run journal。- 新事件使用 canonical event scope:
payload.eventScope.invocationId表示主归属 invocation,payload.eventScope.relatedInvocationIds表示相关 invocation。readEvents({ invocationId })会返回主归属或相关列表命中的事件;旧 journal 没有 canonical scope 时,后端仅为兼容读取历史字段。 timelineProjection仅在includeTimelineProjection = true时返回。它是面向 Timeline UI 的轻量结构投影,不是 journal event;它来自 run 的 invocation/task repository,用于在分页事件缺少 SubAgent task 或 handoff 起点时仍能识别 run 内 Agent graph。普通 polling / subscribe 不应请求该投影。timelineProjection不受invocationId过滤影响;调用方可以同时读取局部事件页和全局 Agent graph。- 当前暂不返回
hasMoreBefore/hasMoreAfter。
9. readWorkspaceFile
type AgentReadWorkspaceFileInput = {
runId: string;
path: string;
};
type AgentWorkspaceFile = {
path: string;
text: string;
chars: number;
words: number;
sha256: string;
};
路径必须是 workspace relative path。非法路径直接 reject。该方法读取当前 run workspace;模型侧读取应使用 workspace_read_file 工具,前端/扩展侧读取应使用本方法。
10. readModelTurn(Project Contract)
type AgentReadModelTurnInput = {
runId: string;
invocationId?: string;
round: number;
maxChars?: number;
};
type AgentModelTurn = {
runId: string;
round: number;
modelResponsePath: string;
provider: {
source?: string;
format?: string;
model?: string;
responseId?: string;
usage?: unknown;
};
assistant: {
text: string;
totalChars: number;
totalWords: number;
truncated: boolean;
};
narration?: {
source: 'assistantText';
text: string;
totalChars: number;
totalWords: number;
truncated: boolean;
} | null;
reasoning: Array<{
source: string;
text: string;
totalChars: number;
totalWords: number;
truncated: boolean;
}>;
toolCalls: Array<{
callId: string;
toolId: string;
name: string;
modelAlias?: string;
}>;
};
narration 是带工具调用的模型回合中可展示给用户的 assistant visible text 投影,用于表达模型在工具调用前后的简短叙述、意图或转场。它不是 runtime status,不从 reasoning / thinking / thought 提取,也不解析 assistant text 内部的 JSON 字段。无工具调用或空文本时为 null 或缺省。
assistant.text、narration.text 与 reasoning[].text 会按 maxChars 截断;totalChars / totalWords 始终表示截断前完整文本的字词统计。
round 必须大于 0。maxChars 省略时由后端使用默认上限;传入时必须大于 0。
invocationId 省略时读取 root invocation;读取 SubAgent / handoff invocation 的模型回合时必须传入对应 invocation id。
该方法返回面向 UI 的白名单投影:assistant 输出、narration、可见/摘要化 reasoning、工具调用摘要与 provider 摘要。它不会暴露完整 raw response、provider-private native continuation、签名或 encrypted reasoning。需要完整诊断时仍使用 run workspace 中的 modelResponsePath 与 LLM API log。
10.1 readTaskDetail(Project Contract)
type AgentReadTaskDetailInput = { runId: string; taskId: string; includeResult?: boolean };
type AgentTaskDetail = {
runId: string;
taskId: string;
parentInvocationId: string;
childInvocationId: string;
targetProfileId: string;
workspaceKey: string;
continuation: 'return_to_parent' | 'transfer_control';
status: 'queued' | 'running' | 'completed' | 'failed' | 'cancelled';
task: { objective: string; title?: string; [key: string]: unknown };
resultRef: string | null;
result: {
summary: string;
summaryRef: string | null;
output: Record<string, unknown>;
} | null;
error: string | null;
};
按任务 ID 读取原始委派 task 或交接 handoff 参数,以及读取时的任务状态、结果引用和错误。两种参数统一放在 task 字段,保留自定义字段;状态不是所选历史事件发生时的快照。空 ID、任务不存在、持久化记录身份不匹配或任务说明格式无效直接 reject。
includeResult 默认 false,此时不读取结果文件,result 为 null。传入 true 且存在结果引用时,后端读取并校验结果的格式与任务身份,返回原始摘要和输出字段;无结果引用时仍为 null,结果文件损坏则直接 reject。旧字段 questionsForParent 统一投影为 questionsForCaller;questionsForCaller 已有非 null 值时优先使用它,其余输出字段保留。该读取和兼容逻辑由 agent.await 与详情 API 共用,不改写持久化文件。
Timeline 只在打开对应详情时调用,不依赖当前 journal 页包含原始工具请求,也不解析 tasks/ 或 agent-results/ 内部文件;返回视图传入 includeResult: true,没有结果时明确显示不可用或任务错误,不重复读取 Markdown 摘要文件。
11. pruneChatPersistentStates
type AgentPruneChatPersistentStatesInput = {
chatRef?: AgentChatRef;
stableChatId?: string;
candidateStateIds: string[];
};
type AgentPruneChatPersistentStatesResult = {
workspaceId: string;
removedStateIds: string[];
};
pruneChatPersistentStates() 是消息/Swipe 删除后的 Host cleanup 入口,不是全量 GC。调用方必须显式传入从被删除消息或被删除 swipe metadata 中收集到的 candidateStateIds;缺失、非数组或空字符串 state id 必须 reject。
后端会重新读取当前完整 chat payload,收集仍被当前聊天消息或 swipe 引用的 retained state ids,只删除 candidateStateIds - retainedStateIds。未被本次删除动作明确列为 candidate 的孤儿 state 必须保留,避免第三方总结、隐藏楼层、并发消息修改或 metadata 损坏把整个 persistent-states 目录误清空。
当前只支持 character chat;group chat persistent state prune 会 fail-fast。删除整个 chat / group chat 时,生命周期服务仍删除对应的完整 Agent chat workspace,这不是本方法的职责。
11.1 copyChatPersistentStates
type AgentCopyChatPersistentStatesInput = {
sourceChatRef: AgentChatRef;
sourceStableChatId: string;
targetChatRef: AgentChatRef;
targetStableChatId: string;
};
copyChatPersistentStates() 是聊天分叉后的 Host lifecycle 操作。它把源 chat workspace 的整个 persistent-states/ 目录复制到新 stable identity 对应的 workspace;不解析或筛选 persistStateId。源目录不存在表示没有状态需要继承,其他 I/O 错误直接传播。
12. retention
type AgentRunPruneRetention = {
keepRecentTerminalRuns: number;
keepFullRecentRuns: number;
};
type AgentRunRetentionSettings = AgentRunPruneRetention & {
autoPruneEnabled: boolean;
};
type AgentRunPruneCandidate = {
runId: string;
workspaceId: string;
stableChatId: string;
chatRef: AgentChatRef;
status: AgentRunStatus;
createdAt: string;
updatedAt: string;
action: 'slim_heavy_artifacts' | 'delete_run';
reason: 'outside_full_retention_window' | 'outside_history_retention_window';
fileCount: number;
byteCount: number;
};
type AgentRunPruneBlockedRun = AgentRunPruneCandidate & {
blockReason: 'active_run' | 'missing_terminal_event' | 'invalid_journal' | 'invalid_storage';
message?: string;
};
type AgentRunPruneFailedRun = AgentRunPruneCandidate & {
message: string;
};
type AgentRunPrunePlan = {
retention: AgentRunPruneRetention;
detailLimit: number;
terminalRunCount: number;
nonTerminalRunCount: number;
blockedRunCount: number;
fullRetainedRunCount: number;
coreRetainedRunCount: number;
slimCandidateCount: number;
deleteCandidateCount: number;
totalSlimFileCount: number;
totalSlimByteCount: number;
totalDeleteFileCount: number;
totalDeleteByteCount: number;
totalCandidateFileCount: number;
totalCandidateByteCount: number;
candidates: AgentRunPruneCandidate[];
blockedRuns: AgentRunPruneBlockedRun[];
candidateDetailsTruncated: boolean;
blockedDetailsTruncated: boolean;
};
type AgentRunPruneApplyResult = {
retention: AgentRunPruneRetention;
detailLimit: number;
slimmedRunCount: number;
deletedRunCount: number;
failedRunCount: number;
removedFileCount: number;
removedByteCount: number;
failedDetailsTruncated: boolean;
failedRuns: AgentRunPruneFailedRun[];
afterPlan: AgentRunPrunePlan;
};
retention.readSettings() 读取 tauritavern-settings.agent.retention 并以 Host ABI 的 camelCase 形态返回。autoPruneEnabled 默认 false,表示 Rust 后端在 TauriTavern 进程运行期间按当前保留策略进行周期性 Agent run 清理。retention.updateSettings() 只保存策略并唤醒后端调度器重读配置,不同步执行清理;两个数量必须是 0..10000 的整数,且 keepFullRecentRuns <= keepRecentTerminalRuns。
retention.planPrune() 是前端访问 plan_agent_run_prune(dto) 的 facade。它只返回 dry-run plan,可使用当前设置,也可传入一次性 retention override;override 中的 autoPruneEnabled 不参与规划,真正的候选只由两个保留数量决定;detailLimit 只限制返回明细,不影响 totals。retention.applyPrune() 访问 apply_agent_run_prune(dto),后端会用同一 retention 重新规划全量候选再执行,不信任前端预览列表;同一服务实例内 apply 会串行执行,避免并发清理同一批 run 造成假失败。单个 run 执行失败会进入 failedRuns 并继续处理后续 candidate,结构性规划错误仍 fail-fast。结果包含 caller detailLimit 下的 afterPlan,供 UI 展示清理后的事实状态。
自动清理由 Rust AgentRunRetentionAutomationService 拥有生命周期,不依赖 Agent System 面板打开,也不使用前端 setInterval。当前策略是启动/设置变更后延迟一次执行,之后固定周期维护;它复用 apply_agent_run_prune 的后端规划与执行路径,不定义第二套删除规则。
13. profiles / promptAssembly / tools
profiles.* 是当前 Agent Profile 管理入口。profiles.list() 的 summary 包含 directRunnable,供前端区分可直接启动的 root-run Profile 与只能作为 SubAgent / handoff target 的 Profile。列表扫描会返回可加载 Profile,同时把单个本地 Profile JSON 文件的内容损坏放入 issues,避免一个坏文件阻塞整个面板;invalidJson 建议用户确认后删除,invalidFileIdentity 可通过 profiles.repairFile({ profileId, action: "normalizeIdentity" }) 尝试规范化文件 header / identity 键(schemaVersion、kind、id),其它 Profile 内容保持原样。若修复后整份 JSON 仍不能按 Agent Profile 契约读取,则拒绝写回并报告错误。invalidProfile 表示主体结构损坏,需要手动修复,不会自动替换为默认 Profile。目录读取失败、非法文件名等仓储契约错误仍然 fail-fast。Profile JSON 中的 preset.mode = "ref" 与 model.mode = "connectionRef" 会影响 prompt assembly 和最终模型连接;model.mode = "requiresConfiguration" 表示 Profile 需要本机重新选择模型,可保存但不可运行;run.stream 是默认 false 的 invocation 流式策略,不升级 Profile v3;run.directRunnable = false 表示该 Profile 不能直接启动,只能通过已实现的非直接入口运行(当前为 return-mode SubAgent)。前端“可作为子 Agent”会写入该非直接运行语义。保存时无效 schema 必须 fail-fast。
profiles.retargetPresetRefs() 是管理态引用迁移 API,用于 preset rename 生命周期。它只更新 preset.mode = "ref" 且精确匹配 from 的 Profile;to preset 必须已经存在,且不能跨 apiId retarget。from 可以已经 dangling。该 API 不会让运行态静默降级;运行和 prompt assembly 仍按 Profile 契约 fail-fast。该操作逐个 Profile 写回,失败后可用同一请求重试;preset rename 流程必须在依赖迁移完成后再删除旧 preset。
profiles.diagnose() 是管理态健康检查 API。它面向“Profile 可加载但外部资源引用不可用”的情况,返回结构化 diagnostics,而不是让面板从异常字符串推断。该 API 不替代运行态 resolver:promptAssembly.prepare()、root run 与 SubAgent 仍按严格 Profile / preset / model contract fail-fast,且不会静默回退当前 UI preset/model。当前第一期覆盖 preset ref 缺失或不支持、model.requiresConfiguration、LLM Connection 缺失或无效;腐坏 JSON、文件 identity 错误等 storage health 仍由 profiles.list().issues 表达。
type AgentProfileSummary = {
id: string;
displayName: string;
description?: string;
directRunnable: boolean;
};
type AgentProfileStorageIssue = {
profileId: string;
fileName: string;
kind: 'invalidJson' | 'invalidFileIdentity' | 'invalidProfile';
recommendedAction?: 'delete' | 'normalizeIdentity';
message: string;
};
type AgentListProfilesResult = {
profiles: AgentProfileSummary[];
issues: AgentProfileStorageIssue[];
};
type AgentProfileHealth = {
profileId: string;
previewAvailable: boolean;
promptAssemblyAvailable: boolean;
directRunAvailable: boolean;
subAgentAvailable: boolean;
diagnostics: AgentProfileDiagnostic[];
};
type AgentProfileDiagnostic = {
code: string;
severity: 'error';
path: string;
message: string;
resource?: {
kind: 'preset' | 'llmConnection' | 'model';
apiId?: string;
name?: string;
id?: string;
modelId?: string;
};
blocks?: Array<'preview' | 'promptAssembly' | 'directRun' | 'subAgent'>;
repairActions?: Array<
'selectPreset'
| 'selectModel'
| 'setModelRequiresConfiguration'
| 'openJsonEditor'
>;
};
promptAssembly.prepare() 调用 Rust prepare_agent_prompt_assembly,返回 currentPromptSnapshot 或 frontendPromptAssembly。promptAssembly.buildSnapshot() 是前端 broker:它只能使用 frozenRunInputSnapshot 内的 promptInputs、worldInfoActivation、macroContext,并调用真实 SillyTavern PromptManager 组装 promptSnapshot.chatCompletionPayload。该 API 是 Agent orchestration 内部边界,不是第三方扩展任意改写 prompt 的入口。
tools.list(Project Contract)
type AgentToolCatalogItem = {
id: string; // canonical ToolId
nativeName: string;
title: string;
description: string;
inputSchema: unknown;
outputSchema?: unknown;
annotations?: unknown;
source: 'builtin' | 'mcp';
registrationId?: string;
serverDisplayName?: string;
permission?: 'off' | 'ask' | 'allow';
};
type AgentListToolsResult = {
tools: AgentToolCatalogItem[];
diagnostics: Array<{ toolId?: string; code: string; message: string }>;
};
tools.list() 返回 builtin catalog 与当前可用于 Agent 的 MCP cached catalog 的 UI 投影。MCP 投影已经应用 registration description override;Profile 面板以 id 编辑 tools.allow、tools.deny 与更高优先级的 tools.toolDescriptions,但不得把返回值当作可修改的 catalog。MCP 只读取 application memory/disk snapshot,不在该命令或 Agent 启动时隐式联网;缺失 snapshot、Paused/Off、目录中缺失或 root input schema 不是显式 object 的工具不会进入 tools,并在 Profile 配置警告中显示 diagnostic。用户通过 MCP Manager discovery/refresh 更新目录及 registration 描述覆盖。
Model alias 属于 invocation snapshot,不进入此 DTO。Profile v3 的所有 tool-keyed 字段只接受 canonical ToolId:builtin 为 builtin:<native-name>,MCP 为 mcp/<registration-uuid>:<native-name>;v1/v2 载入后一次性迁移并写回,不保留 shorthand 双语法。
14. commit
type AgentCommitInput = {
runId: string;
messageId?: string | number;
};
type AgentCommitResult = {
runId: string;
status: AgentRunStatus;
};
Chat commit 不是公开 Host API 方法,而是 runtime Committer 与 host bridge 的内部握手:
- 前台 root / handoff Agent 在首次成功显式 commit 前,会在每轮 tool calls 全部处理后检查最后一个 auto-commit 候选。非流式 write 与 patch 保持旧候选语义;流式 write 清空候选并由真实 chat partial 承载,patch 仍会成为候选。候选若是
.md、.markdown、.txt或.text文件,则自动触发一次replacecommit。 - 模型也可调用
workspace.commit,无参数时默认replace output/main.md;第一次 host-confirmed 显式 commit 后同时停止当前 run 的自动 commit 与 live-write 展示。patch auto-commit 只规范化当前楼层并清空当次 partial,后续 write 仍可继续流入同一楼层。 - 前端 host bridge 校验当前 active chat 与 run 的
chatRef/stableChatId一致。 - bridge 读取当前 workspace 文件并校验
chat_commit_requested.sha256,内容已变化时拒绝提交;同时通过readModelTurn()读取 root / handoff active chain 的可见 reasoning 投影。bridge 先应用上游生成输出保存前后处理,再通过上游saveReply()写入聊天、累计到标准extra.reasoning,最后调用resolve_agent_chat_commit。 - Host 握手只有“已确认提交 / 未确认提交”两种结果且不设置 wall-clock timeout,移动端挂起期间可以继续等待,取消 run 仍会终止等待。未确认提交统一作为可恢复失败且不写 preservation ledger;自动提交继续运行,显式提交返回模型可见 tool error 以便重试。若
saveReply()已成功改写chat[],正文与 reasoning 会保留,本地 reasoning cursor 同步推进以避免重试时重复追加。Rust 侧 journal、状态机等错误仍 fail-fast。 chat_commit_requested不携带persistStateId;该字段只能在workspace.finish成功提交 persistent state 后,由persistent_state_metadata_update_requested/resolve_agent_persistent_state_metadata_update写回同一条 chat message。chat_commit_requested.isExplicit区分正式工具 commit 与 auto checkpoint;Host 只在成功处理isExplicit: true后关闭 live write。partial 本身不产生该事件。chat_commit_completed.isExplicit保留同一来源标记,使完成事件可独立用于分页展示。Timeline 只显示显式 commit 的请求与完成,自动 commit 仍完整写入 journal,失败警告仍显示。旧完成事件缺少标记时,通过已加载的同commitId请求确认显式来源;无法确认时不显示提交成功项。- 首次 commit 按 generation type 创建或改写目标楼层;后续 commit 使用
appendFinal写入完整 postprocessed 目标文本。appendmode 会把本次读取到的文件文本作为 raw 追加贡献累计后再整体处理,避免片段级 regex。 append在本 run 尚无 commit 时不会报错,会创建本 run 的消息楼层。- 自动和显式 commit 都进入 preservation ledger;任何一个成功后发生错误都会保留 chat 输出并进入 partial success。前台 run 在
workspace.finish前仍必须至少成功一次显式workspace.commit;后台 run 可无 chat commit 完成。 - 只有 live partial 而没有 host-confirmed commit 时 ledger 仍为空,因此 Rust terminal 仍是普通
run_failed/run_cancelled;chat 文本的保留不伪造 backend partial-success。
15. Event Envelope
type AgentRunEvent = {
seq: number;
id: string;
runId: string;
timestamp: string;
level: 'debug' | 'info' | 'warn' | 'error';
type: AgentRunEventType;
payload: unknown;
};
事件类型见 docs/Agent/RunEventJournal.md。
Agent event 不等同 SillyTavern eventSource 事件,不得伪装成 GENERATION_* 或 TOOL_CALLS_*。
16. Errors
错误建议结构:
type AgentApiError = {
code: string;
message: string;
runId?: string;
eventSeq?: number;
retryable?: boolean;
details?: unknown;
};
常见 code:
agent.invalid_intent
agent.invalid_profile
agent.policy_violation
agent.not_found
workspace.path_denied
workspace.required_artifact_missing
model.request_failed
tool.policy_denied
commit.cursor_integrity
commit.save_failed
17. Rust Command
start_agent_run(dto)
list_agent_tools()
cancel_agent_run(dto)
list_agent_runs(dto)
plan_agent_run_prune(dto)
apply_agent_run_prune(dto)
read_agent_run_events(dto)
read_agent_workspace_file(dto)
copy_agent_chat_persistent_states(dto)
resolve_agent_chat_commit(dto)
plan_agent_run_prune(dto) 是后端 dry-run command:它按 tauritavern-settings.agent.retention 或调用方传入的一次性 retention override 计算 slim_heavy_artifacts / delete_run 候选和 files/bytes,不执行删除。slim_heavy_artifacts 使用后端 Agent run storage class 统计,分类边界与 TT-Sync 的 Agent run dataset 词汇对齐,但不读取同步 profile 或 dataset selection。dto.detailLimit 控制返回的 candidate/blocked 明细数量,计数与 bytes totals 不受截断影响;blockedRuns 会显式报告 active run、缺失 terminal event、journal/storage 异常等不能安全清理的对象。Command 层必须是薄封装。Agent loop 不写在 command 内。
apply_agent_run_prune(dto) 使用同一 planner 的 execution 模式重新生成执行计划,并以完整候选集执行 slim_heavy_artifacts / delete_run;同一服务实例内 apply 串行化。slim_heavy_artifacts 仅删除 run workspace 内非核心 history 的 artifact,保留 run.json、events.jsonl、run index 与 run summary projection;delete_run 删除 run workspace、run index 与 run summary projection。稳定 persistent-states/ 不属于 run prune 范围。执行结果会返回删除文件/字节统计、失败 run 明细和执行后的 dry-run plan。
后续命令:
approve_agent_tool_call(dto)
read_agent_diff(dto)
rollback_agent_run(dto)
18. Compatibility
Agent Mode off:
Generate()行为不变。ToolManager行为不变。api.chat行为不变。
Agent Mode on:
- 短期可使用 dryRun 生成 prompt snapshot。
- dryRun 不是纯函数,调用方必须理解它仍会触发上游事件。
- dryRun 不返回 payload;Agent adapter 通过事件捕获 payload。
- Agent tool loop 不递归
Generate()。
19. 当前工具与手动验证
当前开放十四个非 delegation 内建工具:
| Canonical name | Model-facing alias | 说明 |
|---|---|---|
chat.search |
chat_search |
搜索当前 run 绑定的聊天。只有 query 必填;可选 limit、role、start_message、end_message、scan_limit。返回 message index、snippet 与 ref。 |
chat.read_messages |
chat_read_messages |
按 0-based message index 读取当前聊天消息;每项可选 1-based start_line、line_count。默认全文,超限返回可续读预览。 |
worldinfo.read_activated |
worldinfo_read_activated |
读取本次 run 的最终激活世界书条目;无参数先列索引,正文项可选 1-based start_line、line_count,默认全文、超限预览。 |
dice.roll |
dice_roll |
为明确的随机、跑团或 roleplay 检定投骰;支持 d6、1d20、3d6+4 与纯数字。默认 Profile 不启用。 |
skill.list |
skill_list |
列出当前 Profile 可见的已安装 Skill 索引摘要。 |
skill.search |
skill_search |
搜索当前 Profile 可见的单个 Skill 内 UTF-8 文本文件,返回 snippet/ref。 |
skill.read |
skill_read |
读取已安装 Skill 内的 UTF-8 文本文件或 1-based 行范围;默认完整 SKILL.md,超限返回可续读预览。 |
workspace.list_files |
workspace_list_files |
列出模型可见 workspace 文件;path 省略、空字符串、.、./ 表示 workspace root |
workspace.search_files |
workspace_search_files |
搜索模型可见 workspace UTF-8 文本文件,返回 snippet/ref |
workspace.read_file |
workspace_read_file |
读取 UTF-8 文本文件并返回行号;只支持 1-based 行范围,默认全文、超限预览;完整读取记录 read-state |
workspace.write_file |
workspace_write_file |
写 UTF-8 文本到 manifest 可写 roots;mode 默认为 replace,append 原样追加并在缺失时创建文件 |
workspace.apply_patch |
workspace_apply_patch |
单文件 old_string / new_string 精确替换,要求已完整读取或由本 run 创建/修改 |
workspace.commit |
workspace_commit |
提交可见 workspace 文件到当前聊天;无参数默认 replace output/main.md;append 将本次读取到的文件文本追加到同一消息 |
workspace.finish |
workspace_finish |
结束工具循环;前台 run 要求已有成功显式 commit,自动 commit 不满足该门槛;后台 run 可直接结束 |
Profile 显式选择且在 MCP Manager 中为 Ask/Allow 的 MCP 工具会加入 invocation snapshot。模型侧 alias 为 mcp__<规范化 server displayName>__<规范化 nativeName>;碰撞使用确定性 __2、__3 后缀,provider 的 64-byte 上限内优先保留工具动作部分。alias 只在当前 snapshot 内解析,实际执行始终使用 binding 中的 canonical ToolId 与原始 native name,不反向解析 alias。
当前暂不提供审批交互:Ask 与 Allow 对 Agent 都表示可直接调用;Off、Paused、registration 删除及调用前撤权仍在 application 边界阻止发送。已知 server/tool error 作为可恢复 tool result 回到模型;outcome_unknown 可能已经产生副作用,因此不回填模型、不自动 retry,并终止当前 run(已有 commit 时沿用现有 partial-success 收尾)。
AgentToolResult.structured、error_code 与 resource_refs 只供 runtime、audit 与 Timeline UI 使用,不作为字段发送给模型。模型看到直接 Text/Markdown;可恢复错误增加 ## Tool error。MCP text 保持原文,structured content 去重后转为 ## Details Markdown,可操作的非文本 content diagnostic 转为 ## Notes,metadata diagnostic 不进入模型。该 content 超过当前 Profile 的 tools.mcpResultInlineCharLimit(默认 50,000)时,完整 JSON 已先以 create-only 语义保存在内部 audit,runtime 另生成同一完整 Markdown 的行可读 .txt 视图。模型收到最多前 3,000 个 Unicode 字符的前缀预览、可读视图路径、字符数及 workspace_read_file / workspace_search_files 的分段读取指引,不接收 audit JSON 路径。.txt 视图会换行超长物理行,精确原始结果仍在 JSON audit 中。该值必须为正整数,并随 resolved Profile 固定到 invocation。tool-results/ 是所有 invocation 可见但永远不可写的 run root。当前仍不存在 shell 或 extension bridge 工具。
模型可修正的工具错误会作为 is_error = true tool result 回填下一轮。宿主级 IO、journal、序列化、取消和模型响应结构错误仍然让 run failed。
推荐最小启动:
await (window.__TAURITAVERN__?.ready ?? window.__TAURITAVERN_MAIN_READY__);
const agent = window.__TAURITAVERN__.api.agent;
const run = await agent.startRunFromLegacyGenerate({
generationType: 'normal',
options: { presentation: 'foreground' },
});
const stop = agent.subscribe(run.runId, event => console.log(event));
更完整的多轮工具循环 smoke 见 docs/CurrentState/AgentFramework.md。