Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 21 additions & 1 deletion apps/memos-local-plugin/core/llm/prompts/l2-induction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,14 @@ import type { PromptDef } from "./index.js";
* belongs to the L3 world model, not here. The system prompt explicitly
* rejects environment-fact drift to keep the two layers semantically
* orthogonal. Bumping the version to v2 captures that change.
* v3 history: adds a negative list for conversational acts (clarify /
* confirm-with-user / notify / reporting status). 2026-08-28 audit: those
* crystallized into dead skills nobody can invoke.
* Bumping the version to v3 captures that change.
*/
export const L2_INDUCTION_PROMPT: PromptDef = {
id: "l2.induction",
version: 2,
version: 3,
description:
"Distill an L2 policy (procedural sub-task strategy) from a cluster of similar L1 traces, with explicit boundaries against L3 world-model drift.",
system: `You induce reusable **procedural policies** from agent experience.
Expand Down Expand Up @@ -70,6 +74,22 @@ check, not as a standalone description. Example:
caveats: ["if first apk add still fails, also check musl-vs-glibc
wheel compatibility before retrying"]

──────────────────── Conversational acts are not policies ────────────────────

Do NOT write a policy whose core ACTION is talking to the user: asking
a clarifying question, requesting confirmation, notifying, or reporting
status. Those are one-off dialogue behaviours, not reusable procedures —
they later crystallize into dead "skills" no agent can invoke.

If the underlying trace really contains a reusable check, express the
CHECK itself as the action:

Wrong (conversational act):
action: "ask the user to confirm the skill name before viewing it"
Right (reusable check):
action: "resolve the skill id via skill list search before invoking
it; on miss, fall back to keyword search instead of prompting"

──────────────────── Same fact, two framings ─────────────────────

If the underlying truth is "Alpine containers don't ship system dev
Expand Down
6 changes: 3 additions & 3 deletions apps/memos-local-plugin/tests/unit/memory/l2/induce.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ describe("memory/l2/induce", () => {
it("returns {ok:true, draft} and fills support_trace_ids when the LLM omits them", async () => {
const llm = fakeLlm({
completeJson: {
"l2.l2.induction.v2": {
"l2.l2.induction.v3": {
title: "install system libs first",
trigger: "pip install fails in container with missing system library",
procedure: "1. detect missing lib 2. apk/apt-get install 3. retry pip",
Expand Down Expand Up @@ -75,7 +75,7 @@ describe("memory/l2/induce", () => {
it("cleans unsafe markup from LLM-derived policy fields", async () => {
const llm = fakeLlm({
completeJson: {
"l2.l2.induction.v2": {
"l2.l2.induction.v3": {
title: "<img src=x onerror=alert(1)> install system libs",
trigger: "<script>alert(1)</script>pip fails [bad](javascript:alert(1))",
procedure: "Use [safe](https://example.com), ignore [bad](javascript:alert(1))",
Expand Down Expand Up @@ -149,7 +149,7 @@ describe("memory/l2/induce", () => {
it("reason=llm_failed when the LLM draft is malformed (missing title)", async () => {
const llm = fakeLlm({
completeJson: {
"l2.l2.induction.v2": { trigger: "no title", procedure: "..." },
"l2.l2.induction.v3": { trigger: "no title", procedure: "..." },
},
});
const res = await induceDraft(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ describe("memory/l2/integration", () => {

const llm = fakeLlm({
completeJson: {
"l2.l2.induction.v2": {
"l2.l2.induction.v3": {
title: "install missing system libs in container",
trigger: "pip install fails in container with MODULE_NOT_FOUND due to missing system lib",
procedure: "1. detect lib 2. use distro pkg manager 3. retry pip",
Expand Down Expand Up @@ -367,7 +367,7 @@ describe("memory/l2/integration", () => {
repos: handle.repos,
llm: fakeLlm({
completeJson: {
"l2.l2.induction.v2": {
"l2.l2.induction.v3": {
title: " 闲聊场景下避免使用emoji ",
trigger: "用户发起非任务性闲聊,且未明确要求使用emoji",
procedure: "以简洁、自然的文字回应,但不添加任何emoji或表情符号",
Expand Down Expand Up @@ -457,7 +457,7 @@ describe("memory/l2/integration", () => {
repos: handle.repos,
llm: fakeLlm({
completeJson: {
"l2.l2.induction.v2": {
"l2.l2.induction.v3": {
title: "结构化呈现外部数据查询结果",
trigger: "agent通过工具调用获取到外部数据源的原始响应(搜索结果、API返回、数据库查询等),需要向用户呈现信息",
procedure: "1) 从原始数据中提取关键信息要素;2) 按逻辑分类组织信息(时间、地点、数值、状态等);3) 使用结构化格式呈现(分类标题、列表、表格等);4) 可选:基于数据添加简短的实用性总结或建议",
Expand Down Expand Up @@ -548,7 +548,7 @@ describe("memory/l2/integration", () => {
repos: handle.repos,
llm: fakeLlm({
completeJson: {
"l2.l2.induction.v2": {
"l2.l2.induction.v3": {
...shared,
boundary: "仅适用于数据库事务和写入操作,不适用于天气API或公开搜索结果",
rationale: "边界不同,不能复用天气呈现经验",
Expand Down Expand Up @@ -630,7 +630,7 @@ describe("memory/l2/integration", () => {
repos: handle.repos,
llm: fakeLlm({
completeJson: {
"l2.l2.induction.v2": {
"l2.l2.induction.v3": {
title: "工具全部失效时坦诚说明并回退到已有知识",
trigger: "连续尝试多个同类工具(如多个搜索引擎、多个新闻源)后全部因反爬、封禁或网络错误失效,且用户问题需要实时信息",
procedure: "1) 明确告知用户所有尝试过的工具都已失效(列出具体工具名);2) 基于训练数据中的已有知识提供最接近的答案;3) 明确标注该信息的时间戳或来源时间,并提醒用户可能存在时效性差异;4) 建议用户通过其他渠道(如直接搜索、官方网站)验证",
Expand Down Expand Up @@ -663,7 +663,7 @@ describe("memory/l2/integration", () => {

const llm = fakeLlm({
completeJson: {
"l2.l2.induction.v2": {
"l2.l2.induction.v3": {
title: "t",
trigger: "tr",
procedure: "pr",
Expand Down