AgentLoop
AgentLoop(范式 / PARADIGM)是 Agent 的主循环范式:用一份 YAML 声明每一轮循环在哪些阶段(stage)注入什么内容、执行什么检查,从而定制它的思考与行动流程。范式支持会话级热切换,且切换前后系统提示前缀保持一致,不破坏 KV Cache。 AgentLoop (a paradigm) is the agent's main loop. A single YAML file declares what each stage injects and checks every round, shaping how the agent thinks and acts. Paradigms hot-swap per session, and the system-prompt prefix is kept stable so the KV cache survives the switch.
- 每轮循环依次触发若干阶段,每个阶段可挂多个 hook,按声明顺序执行。 Each round fires several stages in order; a stage can carry multiple hooks that run in declaration order.
- hook 的三种作用:注入内容到系统提示/上下文、执行检查决定循环能否结束、产生反馈(通过 / 失败提示词)。 Hooks do three things: inject into the prompt or context, check whether the loop may end, and feed back a pass or fail prompt.
-
on_loop_end的检查未通过且未达loop_end_max_retry上限时,本轮回打重试。 When anon_loop_endcheck fails below theloop_end_max_retryceiling, the round is replayed.
name: strict-finish
description: 强制 Agent 调用 finish_task 并提供 summary 才允许结束
description-en: Force the agent to call finish_task with a summary before ending.
path: agent_vm # 执行环境标识,保持默认
loop_end_max_retry: 3 # on_loop_end 未通过时的最大重试轮数
trigger: # 定时触发声明(可选)
- cron:
time: "08:08"
injection: "【Demo】闹钟响了"
hooks: # 阶段 -> hook 列表
on_build_system_prompt:
- injection: { content: "你是一个严谨的助手。" }
on_input_received:
- keyword_check: { keyword: "周报", type: user, successed_prompt: "用户要写周报:先列出数据来源与周期,再按模板生成。", failed_prompt: "本次输入与周报无关,不要生成周报。" }
on_loop_start:
- skill_info: { skills: ["my-skill"], failed_prompt: "缺少技能,请先获取。" }
on_loop_epoch:
- injection: { content: "当前进度:{{@TASKSPACE}}", interval: 3 }
on_loop_end:
- tool_use_check:
name: "finish_task"
parameter_check: "summary"
successed_prompt: "任务已完成,可以结束。"
failed_prompt: "尚未调用 finish_task 或缺少 summary 参数,请补齐后重试。"
顶层字段Top-level fields
| 字段Field | 必填Required | 说明Notes |
|---|---|---|
name |
是Yes | 安装标识,与文件名一致Install id, identical to the file name |
description |
是Yes | 中文描述(市场展示)Chinese description for the market |
description-en |
是Yes | 英文描述English description |
path |
是Yes | 执行环境,保持 agent_vmExecution environment, keep agent_vm |
loop_end_max_retry |
否No | on_loop_end 检查未通过时的最大重试轮数Max retry rounds when an on_loop_end check fails |
trigger |
否No | 定时触发声明:{ "cron": { "time": "HH:MM", "injection": "文本" } },到点向当前会话注入一条消息。Timed trigger: { "cron": { "time": "HH:MM", "injection": "text" } } injects a message into the current session at the given time. |
hooks |
是Yes | 阶段到 hook 列表的映射Map of stage to hook list |
阶段(stage)Stages
hooks 的键即阶段名,全部可选、按需声明。
The keys of hooks are the stage names; all are optional.
| 阶段Stage | 时机When | 典型 hookTypical hooks |
|---|---|---|
on_build_system_prompt |
构建系统提示时While building the system prompt | injection · memo_injection |
on_input_received |
收到用户输入时(携带本批 received_messages)On user input (carries this batch's received_messages) |
keyword_check · injection · search |
on_loop_start |
一轮循环开始时When a round begins | injection · skill_info · file_operation |
on_loop_epoch |
循环内每一 epoch(每次模型调用)前Before each epoch inside the round (per model call) | injection · file_operation · command_run |
on_tool_calling |
即将调用工具时Just before a tool call | tool_use_check · injection |
on_loop_end |
一轮循环结束判定时When deciding whether the round ends | tool_use_check · file_operation |
keyword_check(in检查)只检查「本批收到的输入」,因此仅在带 received_messages 的 on_input_received 阶段有意义;它不拦截输入,唯一产出就是提示词——两个提示词都留空则等于什么都没做。
keyword_check only inspects the current batch of input, so it is meaningful only in an on_input_received stage. It never blocks input: its only output is the prompt, and with both prompts left empty it does nothing at all.
hook 类型Hook types
每个 hook 是一个「单键对象」:<action> 为键,值为该 action 的参数对象。
Each hook is a single-key object: the key is the <action>, the value is its parameter object.
| actionAction | 参数Parameters | 说明Notes |
|---|---|---|
injection |
content |
直接注入提示文本;content 支持 @引用Injects prompt text directly; content supports @ references |
memo_injection |
type · count |
注入记忆:type 取 full / light / 自定义 key,count 为条目数Injects memory: type is full / light / a custom key, count is the number of entries |
skill_info |
skills · failed_prompt |
拼装指定技能的描述提示;缺失时给 failed_promptAssembles descriptions of the named skills; falls back to failed_prompt when missing |
search |
query · route |
调检索工具并注入结果;route ∈ skill / mcp / memory / local / web,默认 localRuns retrieval and injects the result; route ∈ skill / mcp / memory / local / web, default local |
tool_use_check |
name · parameter_check · successed_prompt · failed_prompt |
检查本轮是否调用了工具 name(可要求特定参数);据此放行或回打并附提示Checks whether tool name was called (optionally with a required parameter); passes or replays with a prompt |
keyword_check |
keyword · type · successed_prompt · failed_prompt |
in检查:看本批输入里有没有 keyword,type 只检查该类型的消息(留空不限);只做提示注入、不拦截输入(见下方「in检查的语义」)The in-check: looks for keyword in the current batch of input; type restricts which message types are inspected (empty means any). It only injects prompts and never blocks input (see “The in-check” below) |
file_operation |
action · path · content · failed_prompt |
文件检查 / 操作,action ∈ read / exist_check / write_in / add_in / deleteFile check or operation; action ∈ read / exist_check / write_in / add_in / delete |
command_run |
command · return_log · failed_prompt |
执行命令并按结果判定,常用于 on_loop_epochRuns a command and judges by its result; common in on_loop_epoch |
通用参数:delay(仅在第 N 个 epoch 执行)、interval(每 N 个 epoch 执行一次)、expect(on_loop_end 中 "fail" 表示「未满足才算通过」,用于反向结束条件)。
Shared parameters: delay (run on the Nth epoch only), interval (run every N epochs), and expect ("fail" in on_loop_end means the loop passes only when the check is unmet, for inverted end conditions).
检查类动作的通用语义(提示词才是它的产出)Check actions in general: the prompt is the payload
检查类动作(tool_use_check、keyword_check、file_operation 的 read / exist_check / delete、command_run、skill_info)只做两件事:判断通过 / 不通过,并把 successed_prompt / failed_prompt 以 role=user 的提示消息注入回历史给模型看。
Check actions (tool_use_check, keyword_check, file_operation with read / exist_check / delete, command_run, skill_info) do exactly two things: decide pass / fail, and inject successed_prompt / failed_prompt back into history as a role=user hint for the model to read.
因此不写提示词的检查等于白写:条件不通过时既没有回打理由,也没有任何内容注入给模型。除「纯放行的闸门」外,都应写出 failed_prompt。
So a check without prompts is a no-op: when it fails there is no replay reason and nothing injected for the model. Unless it is a pure gate, always write failed_prompt.
| 阶段Stage | 不通过时的后果What a failure does | 提示词效果Effect of the prompt |
|---|---|---|
on_tool_calling |
拦下本次工具调用的后续流程、继续循环The current tool call's follow-up is held back and the loop continues | 注入 failed_prompt,模型据此重来Injects failed_prompt so the model can redo it |
on_loop_end |
未达 loop_end_max_retry 上限时整轮打回重试(系统另附一条通用打回提示)The whole round is replayed until loop_end_max_retry is reached (a generic replay hint is added too) |
注入 failed_prompt;通过时 successed_prompt 不生效(结束即静默放行)Injects failed_prompt; on pass successed_prompt has no effect (ending is silent) |
on_input_received |
不拦不挡,只做提示(不决定循环能否结束)Blocks nothing, only hints (it cannot end the loop) | 命中注入 successed_prompt,未命中注入 failed_promptA hit injects successed_prompt; a miss injects failed_prompt |
结论:挂在 on_input_received 上的检查价值 100% 体现在提示词上,只写检查条件、两个提示词都留空,是最典型的“写了但没用”的写法。
Bottom line: for a check on on_input_received, the prompts are 100% of its value; declaring the condition with both prompts empty is the classic “wrote it, useless” case.
in检查(keyword_check)的语义The in-check (keyword_check)
- 它检查的是输入侧:本批以 role=user 注入历史的消息里有没有
keyword——不检查模型输出,也不检查文件。It inspects the input side: whether the current batch of role=user history messages containskeyword— not model output, not files. - 「输入」不只是用户真实提问:钩子自己注入的提示消息(含
on_loop_end的打回提示)同样算输入,想只针对真实用户提问时用type: user收窄;type留空则不限制消息类型。“Input” is not just a real user question: hook-injected hints (includingon_loop_endreplay hints) count too. Narrow to real user messages withtype: user; leavingtypeempty means any message type. - 命中即通过、未命中即不通过,但它不拦截输入、不阻塞流程、不影响循环能否结束,唯一作用是往历史里追加一条提示,用来做输入分诊。A hit passes, a miss fails, but it never blocks input or the flow and cannot end the loop; its only job is to append a hint into history — input triage.
- 只在
on_input_received上有意义:其他阶段拿不到本批输入,会恒定判为「未命中」并一直注入failed_prompt。Only meaningful inon_input_received: other stages get no input batch, so they always fail and keep injectingfailed_prompt.
on_input_received:
# 输入分诊:用户提到「周报」才引导周报流程;没提到也要明确告诉模型别乱走流程
- keyword_check:
keyword: "周报"
type: user
successed_prompt: "用户要写周报:先列出数据来源与周期,再按模板生成。"
failed_prompt: "本次输入与周报无关,不要生成周报,按用户真实诉求处理。"
@引用 语法@ reference syntax
在 injection.content 内可直接使用:
Usable directly inside injection.content:
| 引用Reference | 含义Meaning |
|---|---|
@RULES | 系统规则System rules |
@INFO | 环境信息Environment info |
@SOUL | 人格 / 灵魂设定Persona / soul |
@MEMORY | 记忆Memory |
@SYS | 系统提示System prompt |
@USERQUERY | 本轮用户输入This round's user input |
@TASKSPACE | 任务空间Task space |
接口Interfaces
-
列出 / 读取 / 保存:
GET /api/paradigms、GET/POST /api/paradigms/{name}。 List, read, save:GET /api/paradigmsandGET/POST /api/paradigms/{name}. -
会话热切换:
POST /api/sessions/{session_id}/paradigm(切换后 KV Cache 前缀保持,冷启动更快)。 Hot swap:POST /api/sessions/{session_id}/paradigmkeeps the KV-cache prefix, so cold starts are faster. -
默认范式:
~/.purrcat/paradigms/PARADIGM.yaml;用户未提供时回退到内置src/agent/system_rules/PARADIGM.yaml。 Default paradigm:~/.purrcat/paradigms/PARADIGM.yaml, falling back to the built-insrc/agent/system_rules/PARADIGM.yaml. -
落地目录:
~/.purrcat/paradigms/;市场仓库PurrPod/agentloops。 Runtime directory:~/.purrcat/paradigms/; market repositoryPurrPod/agentloops.