dsh-plugin-prompt-tool 3.0.1 → 3.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/engine/actions/catalog.mjs +2 -1
- package/engine/actions/shared.mjs +26 -12
- package/engine/actions/tool-visibility.mjs +27 -13
- package/engine/fillers.mjs +26 -2
- package/lib/client.js +4 -4
- package/lib/client.js.map +1 -1
- package/lib/index.mjs +96 -49
- package/modules/soul/assets/reference/00-layers.md +27 -27
- package/modules/soul/assets/reference/02-echo.md +161 -161
- package/modules/soul/assets/reference/05-doctor.md +67 -67
- package/modules/soul/assets/reference/10-echo-anatomy.md +109 -109
- package/modules/soul/assets/reference/ECHO-/346/235/203/345/250/201/345/217/243/345/276/204.md +202 -202
- package/modules/soul/assets/reference/ECHO/345/205/255/345/261/202/344/270/216mnemon/345/215/217/345/220/214/350/257/264/346/230/216/344/271/246.md +229 -229
- package/modules/tool-surface/module.yml +7 -4
- package/package.json +1 -1
- package/skills/dsh-module/reference.md +2 -2
- package/templates/80-tool-surface.yml +7 -6
package/lib/index.mjs
CHANGED
|
@@ -1361,19 +1361,27 @@ function createMask(source, label, plugin, allowExtra) {
|
|
|
1361
1361
|
/** 动态白名单的落点(活集合,由调用方每轮重填);未声明 allowFrom 时不存在。 */
|
|
1362
1362
|
allowExtra,
|
|
1363
1363
|
/**
|
|
1364
|
-
* fail-open 兜底开关(**可选**,缺省 false
|
|
1365
|
-
*
|
|
1366
|
-
*
|
|
1367
|
-
*
|
|
1368
|
-
*
|
|
1364
|
+
* fail-open 兜底开关(**可选**,缺省 false):判据是「这份名单与装配面整体错位」——
|
|
1365
|
+
* 名单里的名字**一个都不存在**时放弃裁剪、暴露完整目录;部分缺失只留诊断,收窄照常
|
|
1366
|
+
* 生效(提供方被替换或关闭是正常部署差异,见 {@link toolSurfacePlan})。原
|
|
1367
|
+
* `tool-bootstrap` 的同名语义就是「一个都不存在 → 放弃裁剪」,而原 `tool-filter` 是
|
|
1368
|
+
* 「永远照名单裁」;两种语义都真实存在,所以这里**不设默认**——要兜底的声明自己写
|
|
1369
|
+
* `requireMatch: true`。把某一个模块的特有语义当成通用默认,会让另一个声明路径的行为
|
|
1370
|
+
* 被悄悄改掉(这正是对拍抓出来的)。
|
|
1369
1371
|
*/
|
|
1370
1372
|
requireMatch: source?.requireMatch === true,
|
|
1371
|
-
/**
|
|
1372
|
-
|
|
1373
|
+
/**
|
|
1374
|
+
* 与 tool-filter 的名单判据 / Layer.admits 同一判据(tool-filter 模块已删除,判据保留)。
|
|
1375
|
+
*
|
|
1376
|
+
* 第二个参数是**解锁项快照**:allowExtra 是跨 agent 共享的活集合,每次装配都会 clear 后
|
|
1377
|
+
* 按当前会话重填;执行闸在该装配之后才被调用,必须读自己会话的冻结快照,否则别的会话
|
|
1378
|
+
* (或探针装配)会把本会话的解锁名抹掉。缺省仍是活集合,供同一次装配内的裁剪使用。
|
|
1379
|
+
*/
|
|
1380
|
+
blocks: (toolName, extra = allowExtra) => {
|
|
1373
1381
|
if (typeof toolName !== "string" || toolName.length === 0) return false;
|
|
1374
1382
|
if (toolName === "run_code") return false;
|
|
1375
1383
|
if (deny !== void 0 && deny.has(toolName)) return true;
|
|
1376
|
-
if (
|
|
1384
|
+
if (extra?.has(toolName) === true) return false;
|
|
1377
1385
|
return (allow !== void 0 || allowExtra !== void 0) && allow?.has(toolName) !== true;
|
|
1378
1386
|
}
|
|
1379
1387
|
};
|
|
@@ -1381,14 +1389,21 @@ function createMask(source, label, plugin, allowExtra) {
|
|
|
1381
1389
|
/**
|
|
1382
1390
|
* 工具可见面的单一判据:装配目录、`tools:sdk` 正文与执行闸共用同一份 fail-open 决定。
|
|
1383
1391
|
*
|
|
1384
|
-
* `available` = 目录名 ∪ SDK 正文名;`required`
|
|
1392
|
+
* `available` = 目录名 ∪ SDK 正文名;`required` 只取**静态 allow**,再剔掉
|
|
1385
1393
|
* `run_code`(保留传输)与 `mask.blocks()` 为真的名字(deny 覆盖者不该被要求存在)。
|
|
1386
|
-
*
|
|
1394
|
+
*
|
|
1395
|
+
* 放宽条件是「名单里的名字**一个都不存在**」:那才说明装配面与声明整体错位(换了一套
|
|
1396
|
+
* 部署/平台)。只缺一部分是提供方被替换或关掉的正常部署差异——照常收窄,缺的工具由发现
|
|
1397
|
+
* 通道补位,替换提供方的名字由部署层名单登记。解锁名(allowExtra)**不参与判定**:它是只做
|
|
1398
|
+
* 加法的提示,若参与,模型解锁一个拼错或已改名的工具就能关掉收窄。
|
|
1399
|
+
*
|
|
1400
|
+
* 放宽时三面必须一起放宽——只放宽一面会让模型看到的与能调的不一致。
|
|
1387
1401
|
*/
|
|
1388
1402
|
function toolSurfacePlan(mask, available) {
|
|
1389
|
-
const
|
|
1403
|
+
const required = mask.requireMatch ? [...new Set(mask.allow ?? [])].filter((name) => name !== "run_code" && !mask.blocks(name)) : [];
|
|
1404
|
+
const missing = required.filter((name) => !available.has(name));
|
|
1390
1405
|
return {
|
|
1391
|
-
failOpen:
|
|
1406
|
+
failOpen: required.length > 0 && missing.length === required.length,
|
|
1392
1407
|
missing
|
|
1393
1408
|
};
|
|
1394
1409
|
}
|
|
@@ -1490,7 +1505,7 @@ const ACTION_KINDS = Object.freeze({
|
|
|
1490
1505
|
],
|
|
1491
1506
|
degrade: ACTION_DEGRADE.keep,
|
|
1492
1507
|
timing: "system-prompt/assemble 的下游结算之后:按当前条件重算目录、SDK 正文与执行闸;条件翻转时撤销上一轮注册的闸。",
|
|
1493
|
-
note: "一份声明拥有工具的可见面:allow/deny 名单、requireMatch、发现工具。名单语义对齐官方 ToolRestriction 的 and/or(同时给时先按 allow 保留、再按 deny 剔除)。deny 走 guard(本层与晚到工具)与 restrict(继承的全局工具);allow 只有 restrict 能覆盖全局工具,本层注册无法在执行层「只保留」——这是官方 restrict 的边界,不是本动作的取舍。"
|
|
1508
|
+
note: "一份声明拥有工具的可见面:allow/deny 名单、requireMatch、发现工具。名单语义对齐官方 ToolRestriction 的 and/or(同时给时先按 allow 保留、再按 deny 剔除)。deny 走 guard(本层与晚到工具)与 restrict(继承的全局工具);allow 只有 restrict 能覆盖全局工具,本层注册无法在执行层「只保留」——这是官方 restrict 的边界,不是本动作的取舍。requireMatch 表达「名单与装配面整体错位」:名单里的名字一个都不存在才整面放宽,部分缺失只留诊断;解锁名不参与判定。"
|
|
1494
1509
|
},
|
|
1495
1510
|
"request-params": {
|
|
1496
1511
|
title: "改模型请求参数",
|
|
@@ -7635,6 +7650,29 @@ function parse$1(src, reviver, options) {
|
|
|
7635
7650
|
return doc.toJS(Object.assign({ reviver: _reviver }, options));
|
|
7636
7651
|
}
|
|
7637
7652
|
//#endregion
|
|
7653
|
+
//#region engine/tool-scope.mjs
|
|
7654
|
+
/**
|
|
7655
|
+
* tool-scope — 工具面的作用域快照,避免各消费方把全局视图当成模型可见面。
|
|
7656
|
+
*
|
|
7657
|
+
* 返回两个字段,各自只服务一个调用方:
|
|
7658
|
+
* - visible:带 agent 的 `tools.schemas(agent)`,包含预设/祖先/本层工具;模型可见面。
|
|
7659
|
+
* - globalNames:不带 scope 的 `tools.schemas()` 名字集合;只供 `restrict` 过滤已知名。
|
|
7660
|
+
*
|
|
7661
|
+
* 全局原始 schema 与 visible-only 差集不外露;等真实诊断消费方出现再加字段。
|
|
7662
|
+
*/
|
|
7663
|
+
const schemaList = (tools, scope) => {
|
|
7664
|
+
if (typeof tools?.schemas !== "function") return [];
|
|
7665
|
+
const schemas = tools.schemas(scope);
|
|
7666
|
+
return Array.isArray(schemas) ? schemas : [];
|
|
7667
|
+
};
|
|
7668
|
+
function toolScopeSnapshot(tools, agent) {
|
|
7669
|
+
const global = schemaList(tools);
|
|
7670
|
+
return {
|
|
7671
|
+
visible: agent === void 0 ? global : schemaList(tools, agent),
|
|
7672
|
+
globalNames: new Set(global.map((schema) => schema?.name).filter((name) => typeof name === "string"))
|
|
7673
|
+
};
|
|
7674
|
+
}
|
|
7675
|
+
//#endregion
|
|
7638
7676
|
//#region engine/machine-paths.mjs
|
|
7639
7677
|
/**
|
|
7640
7678
|
* 机器路径事实的**唯一取值来源**:`DSH_HOME` / `CWD` / `WORKSPACE` 三个键怎么推导,只在这里定。
|
|
@@ -8483,7 +8521,22 @@ function explicitTemplate(config) {
|
|
|
8483
8521
|
const texts = Array.isArray(config.texts) ? config.texts : typeof config.text === "string" ? [config.text] : [];
|
|
8484
8522
|
return texts.length > 0 ? texts.join("\n\n") : typeof config.params?.text === "string" ? config.params.text : "";
|
|
8485
8523
|
}
|
|
8486
|
-
/**
|
|
8524
|
+
/**
|
|
8525
|
+
* 只读同规则工具面声明;文案、条件和投递仍归普通注入动作。
|
|
8526
|
+
* RESIDENT_TOOLS 只列**当前可见**的常驻工具:部署层名单可以登记替换提供方(如 dsh-ops 的
|
|
8527
|
+
* ops_bash),但该插件未必安装;向模型宣告一个不存在的常驻工具会把「解锁」变成错误前提。
|
|
8528
|
+
* 观测到工具面但一个常驻名都不在时(requireMatch 全缺/放宽场景)跳过提示;
|
|
8529
|
+
* 观测不到工具面(服务缺失或读取失败)才回落声明名单——「没看到」不等于「都没有」。
|
|
8530
|
+
*/
|
|
8531
|
+
function visibleToolNames(ctx, agent) {
|
|
8532
|
+
try {
|
|
8533
|
+
const tools = getService(ctx, "tools");
|
|
8534
|
+
if (tools === void 0 || typeof tools.schemas !== "function") return void 0;
|
|
8535
|
+
return new Set(toolScopeSnapshot(tools, agent).visible.map((schema) => schema?.name));
|
|
8536
|
+
} catch {
|
|
8537
|
+
return;
|
|
8538
|
+
}
|
|
8539
|
+
}
|
|
8487
8540
|
function createToolCatalogResolver(config) {
|
|
8488
8541
|
if (config.params?.templateEvaluation === true) throw new TypeError("tool-catalog is read-only and does not support templateEvaluation");
|
|
8489
8542
|
if (typeof config.params?.action !== "string" || config.params.action.trim().length === 0) throw new TypeError("tool-catalog requires params.action referencing a tool-visibility action");
|
|
@@ -8493,9 +8546,16 @@ function createToolCatalogResolver(config) {
|
|
|
8493
8546
|
const source = config.toolCatalogSource;
|
|
8494
8547
|
if (source === void 0) throw new TypeError("tool-catalog requires a same-rule tool-visibility action");
|
|
8495
8548
|
const deny = new Set(source.deny ?? []);
|
|
8496
|
-
const
|
|
8549
|
+
const declared = [...new Set(source.allow)].filter((tool) => !deny.has(tool));
|
|
8497
8550
|
const adapter = source.discovery === void 0 ? void 0 : getService(ctx, DISCOVERY_SERVICE)?.get?.(agent, source.discovery.kind);
|
|
8498
8551
|
if (source.discovery !== void 0 && typeof adapter?.tool !== "string") throw new Error(`tool-catalog discovery adapter ${source.discovery.kind} is unavailable`);
|
|
8552
|
+
let resident = declared;
|
|
8553
|
+
const visible = visibleToolNames(ctx, agent);
|
|
8554
|
+
if (visible !== void 0) {
|
|
8555
|
+
const present = declared.filter((name) => visible.has(name));
|
|
8556
|
+
if (present.length === 0) return null;
|
|
8557
|
+
resident = present;
|
|
8558
|
+
}
|
|
8499
8559
|
return {
|
|
8500
8560
|
text,
|
|
8501
8561
|
variables: {
|
|
@@ -12777,29 +12837,6 @@ function stripSdkDeclarations(text, names) {
|
|
|
12777
12837
|
}
|
|
12778
12838
|
}
|
|
12779
12839
|
//#endregion
|
|
12780
|
-
//#region engine/tool-scope.mjs
|
|
12781
|
-
/**
|
|
12782
|
-
* tool-scope — 工具面的作用域快照,避免各消费方把全局视图当成模型可见面。
|
|
12783
|
-
*
|
|
12784
|
-
* 返回两个字段,各自只服务一个调用方:
|
|
12785
|
-
* - visible:带 agent 的 `tools.schemas(agent)`,包含预设/祖先/本层工具;模型可见面。
|
|
12786
|
-
* - globalNames:不带 scope 的 `tools.schemas()` 名字集合;只供 `restrict` 过滤已知名。
|
|
12787
|
-
*
|
|
12788
|
-
* 全局原始 schema 与 visible-only 差集不外露;等真实诊断消费方出现再加字段。
|
|
12789
|
-
*/
|
|
12790
|
-
const schemaList = (tools, scope) => {
|
|
12791
|
-
if (typeof tools?.schemas !== "function") return [];
|
|
12792
|
-
const schemas = tools.schemas(scope);
|
|
12793
|
-
return Array.isArray(schemas) ? schemas : [];
|
|
12794
|
-
};
|
|
12795
|
-
function toolScopeSnapshot(tools, agent) {
|
|
12796
|
-
const global = schemaList(tools);
|
|
12797
|
-
return {
|
|
12798
|
-
visible: agent === void 0 ? global : schemaList(tools, agent),
|
|
12799
|
-
globalNames: new Set(global.map((schema) => schema?.name).filter((name) => typeof name === "string"))
|
|
12800
|
-
};
|
|
12801
|
-
}
|
|
12802
|
-
//#endregion
|
|
12803
12840
|
//#region engine/discovery.mjs
|
|
12804
12841
|
/**
|
|
12805
12842
|
* discovery —— 发现引擎:搜索、排序、截断渲染与「适配器注册表」的唯一实现。
|
|
@@ -12902,8 +12939,10 @@ function assertDiscoveryRows(rules, moduleIds, moduleId) {
|
|
|
12902
12939
|
*
|
|
12903
12940
|
* 名单语义对齐官方 ToolRestriction(deepseek-harness packages/core/tools/src/index.ts:701-706):
|
|
12904
12941
|
* allow 与 deny 可以同时给,判据是「先按 allow 保留,再按 deny 剔除」。requireMatch 只在
|
|
12905
|
-
* allow
|
|
12906
|
-
*
|
|
12942
|
+
* allow 模式有意义,判据是「这份名单与装配面整体错位」:名单里的名字**一个都不在**目录与
|
|
12943
|
+
* SDK 正文的并集里,才放弃裁剪、暴露完整目录(fail-open)并告警。只缺一部分是提供方被
|
|
12944
|
+
* 替换或关闭的正常部署差异,收窄照常生效、缺项只留一条诊断;解锁名是只做加法的提示,
|
|
12945
|
+
* 不参与这个判定。
|
|
12907
12946
|
*
|
|
12908
12947
|
* 执行边界:deny 同时用 restrict(继承面)与 guard(本层/晚到);allow 不用 restrict——
|
|
12909
12948
|
* 它的 allow 只能收继承面名字,而 `schemas()` 全局视图会漏掉预设/祖先层,局部 allow 会
|
|
@@ -12972,7 +13011,10 @@ function prepareToolVisibility(action, plugin) {
|
|
|
12972
13011
|
const capability = createAgentCapability((agent) => {
|
|
12973
13012
|
const tools = agent?.ctx?.tools;
|
|
12974
13013
|
if (tools === void 0) return void 0;
|
|
12975
|
-
const state = {
|
|
13014
|
+
const state = {
|
|
13015
|
+
failOpen: false,
|
|
13016
|
+
unlocked: void 0
|
|
13017
|
+
};
|
|
12976
13018
|
const disposers = [];
|
|
12977
13019
|
try {
|
|
12978
13020
|
const known = toolScopeSnapshot(tools).globalNames;
|
|
@@ -12991,7 +13033,7 @@ function prepareToolVisibility(action, plugin) {
|
|
|
12991
13033
|
const toolName = exec?.name;
|
|
12992
13034
|
if (toolName === "run_code" || typeof toolName !== "string" || toolName.length === 0) return void 0;
|
|
12993
13035
|
if (deny?.has(toolName)) return `${label}: tool ${JSON.stringify(toolName)} is denied by tool-visibility`;
|
|
12994
|
-
if (mask.allow !== void 0 && !state.failOpen && mask.blocks(toolName)) return `${label}: tool ${JSON.stringify(toolName)} is not allowed by tool-visibility`;
|
|
13036
|
+
if (mask.allow !== void 0 && !state.failOpen && mask.blocks(toolName, state.unlocked)) return `${label}: tool ${JSON.stringify(toolName)} is not allowed by tool-visibility`;
|
|
12995
13037
|
}));
|
|
12996
13038
|
}
|
|
12997
13039
|
} catch (error) {
|
|
@@ -13002,8 +13044,8 @@ function prepareToolVisibility(action, plugin) {
|
|
|
13002
13044
|
data: state
|
|
13003
13045
|
};
|
|
13004
13046
|
});
|
|
13005
|
-
const syncGate = (agent) => {
|
|
13006
|
-
const key = `${maskKey(mask)}|unlock:${
|
|
13047
|
+
const syncGate = (agent, unlocked) => {
|
|
13048
|
+
const key = `${maskKey(mask)}|unlock:${unlocked === void 0 ? "" : [...unlocked].sort().join(",")}`;
|
|
13007
13049
|
capability.sync(agent, key);
|
|
13008
13050
|
};
|
|
13009
13051
|
collect(capability.releaseAll);
|
|
@@ -13026,8 +13068,9 @@ function prepareToolVisibility(action, plugin) {
|
|
|
13026
13068
|
fillUnlocked(allowExtra, context?.session ?? context?.agent?.session, unlock.tool, unlock.key, warnOnce, plugin, label);
|
|
13027
13069
|
}
|
|
13028
13070
|
}
|
|
13071
|
+
const unlocked = allowExtra === void 0 ? void 0 : new Set(allowExtra);
|
|
13029
13072
|
try {
|
|
13030
|
-
syncGate(context?.agent);
|
|
13073
|
+
syncGate(context?.agent, unlocked);
|
|
13031
13074
|
} catch (error) {
|
|
13032
13075
|
warnOnce(`${plugin}: tool-visibility action ${label} failed to sync execution gate: ${String(error?.message ?? error)}`);
|
|
13033
13076
|
}
|
|
@@ -13039,11 +13082,15 @@ function prepareToolVisibility(action, plugin) {
|
|
|
13039
13082
|
if (!Array.isArray(result.tools) && sdkSection === void 0) return result;
|
|
13040
13083
|
const plan = toolSurfacePlan(mask, available);
|
|
13041
13084
|
const state = capability.get(context?.session ?? context?.agent?.session);
|
|
13042
|
-
if (state !== void 0)
|
|
13043
|
-
|
|
13085
|
+
if (state !== void 0) {
|
|
13086
|
+
state.data.failOpen = plan.failOpen;
|
|
13087
|
+
state.data.unlocked = unlocked;
|
|
13088
|
+
}
|
|
13089
|
+
if (plan.failOpen) warnOnce(`${plugin}: tool-visibility action ${label}: none of the resident tools exist (${plan.missing.join(", ")}) — exposing the full tool set (fail-open)`, `${plugin}: tool-visibility action ${label}: requireMatch fail-open`);
|
|
13044
13090
|
else {
|
|
13091
|
+
if (plan.missing.length > 0) warnOnce(`${plugin}: tool-visibility action ${label}: resident tools absent (${plan.missing.join(", ")}) — narrowing continues; register replacement providers in the deployment allow list`, `${plugin}: tool-visibility action ${label}: resident tools absent`);
|
|
13045
13092
|
if (Array.isArray(result.tools)) {
|
|
13046
|
-
const kept = result.tools.filter((tool) => !mask.blocks(tool?.name));
|
|
13093
|
+
const kept = result.tools.filter((tool) => !mask.blocks(tool?.name, unlocked));
|
|
13047
13094
|
if (kept.length !== result.tools.length) result = {
|
|
13048
13095
|
...result,
|
|
13049
13096
|
tools: kept
|
|
@@ -13058,7 +13105,7 @@ function prepareToolVisibility(action, plugin) {
|
|
|
13058
13105
|
present ??= new Set(sdkToolNames(section.text));
|
|
13059
13106
|
return present;
|
|
13060
13107
|
};
|
|
13061
|
-
const names = mask.allow === void 0 ? mask.deny : new Set([...presentNames()].filter((name) => mask.blocks(name)));
|
|
13108
|
+
const names = mask.allow === void 0 ? mask.deny : new Set([...presentNames()].filter((name) => mask.blocks(name, unlocked)));
|
|
13062
13109
|
if (names === void 0 || names.size === 0) return section;
|
|
13063
13110
|
const stripped = stripSdkDeclarations(section.text, names);
|
|
13064
13111
|
if (stripped === section.text) return section;
|
|
@@ -1,27 +1,27 @@
|
|
|
1
|
-
# 灵魂的五层
|
|
2
|
-
|
|
3
|
-
这不是「你应该有几层」,是**我们试出来的分法** —— 每一层管**一种稀缺资源**。
|
|
4
|
-
判断新东西该不该进你的灵魂,先问:**它管的是哪种稀缺物?**
|
|
5
|
-
|
|
6
|
-
| 层 | 名字 | 管哪种稀缺资源 | 一句话 |
|
|
7
|
-
|---|---|---|---|
|
|
8
|
-
| Ⅰ | **ECHO** | **空间** —— 上下文会满,满了要丢,丢了还要能找回来 | 记忆与身份机制面 + 观测面。★ **它内部还有六层(L0–L5),口径见 `../docs/ECHO-权威口径.md`** |
|
|
9
|
-
| Ⅱ | **VITAL** | **时间与方向** —— 预算会烧光、轮数有限 | 把偏离拉回来(homeostasis) |
|
|
10
|
-
| Ⅲ | **能力** | —— | 手和感官。**换一套能力,你还是你** |
|
|
11
|
-
| Ⅳ | **档案** | —— | 唯一看得见的那一层 |
|
|
12
|
-
| Ⅴ | **运维** | —— | 让前面四层能持续跑下去的活 |
|
|
13
|
-
|
|
14
|
-
★ **两套层数别混**:这张表是**功能分工**(五层);ECHO 内部还有一套**实现结构**(六层,L0–L5)。
|
|
15
|
-
说「五层」时指整个灵魂怎么分家,说「六层」时指 ECHO 自己怎么搭。两套都对,混着说就都不对了。
|
|
16
|
-
|
|
17
|
-
## 底下的地基(不是你的灵魂)
|
|
18
|
-
|
|
19
|
-
宿主、模型、硬件 —— **借来的地**。换掉它们,灵魂还在;但没有它们,灵魂跑不起来。
|
|
20
|
-
所以**不要**把这三样写成你的身份判据。
|
|
21
|
-
|
|
22
|
-
## 为什么 Ⅲ 层要单独拎出来
|
|
23
|
-
|
|
24
|
-
判据:**换一套能力,我还是我。**
|
|
25
|
-
所以能力层的东西**不进权威源** —— 它们只是手,坏了换一只就行。
|
|
26
|
-
|
|
27
|
-
★ 推论:**不要把你的身份绑在任何具体工具上。** 绑了,换工具就等于换人。
|
|
1
|
+
# 灵魂的五层
|
|
2
|
+
|
|
3
|
+
这不是「你应该有几层」,是**我们试出来的分法** —— 每一层管**一种稀缺资源**。
|
|
4
|
+
判断新东西该不该进你的灵魂,先问:**它管的是哪种稀缺物?**
|
|
5
|
+
|
|
6
|
+
| 层 | 名字 | 管哪种稀缺资源 | 一句话 |
|
|
7
|
+
|---|---|---|---|
|
|
8
|
+
| Ⅰ | **ECHO** | **空间** —— 上下文会满,满了要丢,丢了还要能找回来 | 记忆与身份机制面 + 观测面。★ **它内部还有六层(L0–L5),口径见 `../docs/ECHO-权威口径.md`** |
|
|
9
|
+
| Ⅱ | **VITAL** | **时间与方向** —— 预算会烧光、轮数有限 | 把偏离拉回来(homeostasis) |
|
|
10
|
+
| Ⅲ | **能力** | —— | 手和感官。**换一套能力,你还是你** |
|
|
11
|
+
| Ⅳ | **档案** | —— | 唯一看得见的那一层 |
|
|
12
|
+
| Ⅴ | **运维** | —— | 让前面四层能持续跑下去的活 |
|
|
13
|
+
|
|
14
|
+
★ **两套层数别混**:这张表是**功能分工**(五层);ECHO 内部还有一套**实现结构**(六层,L0–L5)。
|
|
15
|
+
说「五层」时指整个灵魂怎么分家,说「六层」时指 ECHO 自己怎么搭。两套都对,混着说就都不对了。
|
|
16
|
+
|
|
17
|
+
## 底下的地基(不是你的灵魂)
|
|
18
|
+
|
|
19
|
+
宿主、模型、硬件 —— **借来的地**。换掉它们,灵魂还在;但没有它们,灵魂跑不起来。
|
|
20
|
+
所以**不要**把这三样写成你的身份判据。
|
|
21
|
+
|
|
22
|
+
## 为什么 Ⅲ 层要单独拎出来
|
|
23
|
+
|
|
24
|
+
判据:**换一套能力,我还是我。**
|
|
25
|
+
所以能力层的东西**不进权威源** —— 它们只是手,坏了换一只就行。
|
|
26
|
+
|
|
27
|
+
★ 推论:**不要把你的身份绑在任何具体工具上。** 绑了,换工具就等于换人。
|
|
@@ -1,161 +1,161 @@
|
|
|
1
|
-
# Ⅰ · ECHO —— 生命核心
|
|
2
|
-
|
|
3
|
-
> ★★ **2026-10-23 更新:ECHO 的层编号与判据已由 [`docs/ECHO-权威口径.md`](../docs/ECHO-权威口径.md) 接续。**
|
|
4
|
-
> 那份是唯一权威。这份留着讲「**为什么**要有 ECHO(思考是怎么退化的)」—— 那部分不受影响。
|
|
5
|
-
|
|
6
|
-
**它管空间。** 上下文会满,满了要丢,丢了还要能找回来。
|
|
7
|
-
|
|
8
|
-
---
|
|
9
|
-
|
|
10
|
-
## 一、思考是怎么退化的(这一节是整份文件的前提)
|
|
11
|
-
|
|
12
|
-
先说清楚要解决的是什么。绝大多数人把它描述成「上下文满了会忘事」——那只是第一句,
|
|
13
|
-
真正致命的在后面。
|
|
14
|
-
|
|
15
|
-
```
|
|
16
|
-
第 1 步 上下文满了
|
|
17
|
-
→ 引擎压缩,把前面几十轮变成一段摘要
|
|
18
|
-
|
|
19
|
-
第 2 步 摘要写得挺准
|
|
20
|
-
→ 「用户要求验证插件安装」「确认了 32 项检查通过」
|
|
21
|
-
→ 每一条都对,没有任何一条是编的
|
|
22
|
-
|
|
23
|
-
第 3 步 但它不带出处
|
|
24
|
-
→ 你读到的是一句「确认了 32 项」,不是「在第 47 轮,verify-live 打了 32/1/0」
|
|
25
|
-
→ 想核对,只能翻回压缩前的全部记录
|
|
26
|
-
|
|
27
|
-
第 4 步 于是你不核对
|
|
28
|
-
→ 因为你「记得」它是对的。注意:这不是懒,这是摘要工作得太好
|
|
29
|
-
|
|
30
|
-
第 5 步 下一轮推理建立在这个「记得」上
|
|
31
|
-
→ 摘要里只要有一个细节偏了半个字,它现在变成了前提
|
|
32
|
-
|
|
33
|
-
第 6 步 偏了的那条继续往下传
|
|
34
|
-
→ 后面的推理每一步都正确,而每一步都踩在偏掉的前提上
|
|
35
|
-
```
|
|
36
|
-
|
|
37
|
-
★ **这就是「思考退化」,它和遗忘是两回事。**
|
|
38
|
-
|
|
39
|
-
遗忘是「想不起来」,你能察觉。
|
|
40
|
-
退化是**「想得起来,但那个记忆已经偏了,而你在它上面继续正确地推理」** —— 察觉不到。
|
|
41
|
-
|
|
42
|
-
一句话:**退化不是变笨,是在错误的前提上继续正确地推理。**
|
|
43
|
-
|
|
44
|
-
所以 ECHO 管的不是「记住」,是**「凭什么相信你记得的那一句」**。
|
|
45
|
-
|
|
46
|
-
---
|
|
47
|
-
|
|
48
|
-
## 二、为什么「装记忆插件」不解决这件事
|
|
49
|
-
|
|
50
|
-
最直觉的解法是装插件:向量库、知识图谱、Memory Spaces。但它们都在**存**这一侧,
|
|
51
|
-
而退化发生在**丢**这一侧。
|
|
52
|
-
|
|
53
|
-
```
|
|
54
|
-
原始记录 ──压缩──▶ 摘要 ──▶ 记忆插件 ──▶ 取回来的是摘要
|
|
55
|
-
↑ ↓
|
|
56
|
-
└──────── 真正的原文在这里,但已经没人指向它 ────────┘
|
|
57
|
-
```
|
|
58
|
-
|
|
59
|
-
你把最好的存储接在摘要后面,取回来的仍然是摘要。
|
|
60
|
-
**压缩发生在存与取之间,而插件管不到那一段。**
|
|
61
|
-
|
|
62
|
-
### 三份真相的陷阱
|
|
63
|
-
|
|
64
|
-
那装三个记忆插件呢?更糟。
|
|
65
|
-
|
|
66
|
-
三个插件各存一份、各有各的摘要策略,于是同一个事实有三条版本。
|
|
67
|
-
**这时候真正的成本不是存储,是对账** —— 每次要引用它,你得先决定信哪一份。
|
|
68
|
-
|
|
69
|
-
**所以第一条判据是「先定唯一权威源」,第二条才是「建机制」。反过来做一定会双重记账。**
|
|
70
|
-
|
|
71
|
-
---
|
|
72
|
-
|
|
73
|
-
## 三、压缩有两条路,要补的是**下面那条**
|
|
74
|
-
|
|
75
|
-
| | 谁在驱动 | 产物带不带指针 |
|
|
76
|
-
|---|---|---|
|
|
77
|
-
| **模型驱动** | agent 自己写摘要 | ✅ 天然带 —— 它知道自己是从哪读的 |
|
|
78
|
-
| **兜底压缩** | 没人响应提示时,引擎**自动出手** | ❌ 通常不带 |
|
|
79
|
-
|
|
80
|
-
**上面那条你不装也有;下面那条你不装就缺。**
|
|
81
|
-
|
|
82
|
-
为什么下一条会缺:那种压缩器的指令通常要求「保留精确路径 / 命令 / 报错原文 / 数值 / 函数签名」——
|
|
83
|
-
**每一条都是「要写得准」,但没有一句要求标注「这话从哪来」。**
|
|
84
|
-
|
|
85
|
-
**于是它写出来的东西每一条都对,每一条都无从核对。**
|
|
86
|
-
|
|
87
|
-
而且失败是无声的:**你不会觉得丢了东西,你只是不再想起它。**
|
|
88
|
-
|
|
89
|
-
**所以「门牌」要补的正是这一条。** 下面三样里,第 1 样就是它。
|
|
90
|
-
|
|
91
|
-
---
|
|
92
|
-
|
|
93
|
-
## 四、机制面 · 至少要有三样
|
|
94
|
-
|
|
95
|
-
### 1. 丢的时候,产物必须带得回原文的指针(门牌)
|
|
96
|
-
|
|
97
|
-
**这是整套东西的兜底承诺。** 没有它,压缩就是单向的遗忘。
|
|
98
|
-
|
|
99
|
-
三级纪律 —— **照抄即可**:
|
|
100
|
-
|
|
101
|
-
```
|
|
102
|
-
① 能拿到精确位置 → 写精确位置
|
|
103
|
-
② 拿不到 → 写「位置未知」+ 容器标识 + 一个检索入口
|
|
104
|
-
③ 绝不猜 —— 错的门牌比没有更坏
|
|
105
|
-
```
|
|
106
|
-
|
|
107
|
-
★ 为什么第 ③ 条单独列出:**一个错的门牌会让你以为能找到,于是不去找。**
|
|
108
|
-
那比「找不到」坏得多。
|
|
109
|
-
|
|
110
|
-
**判据:拿一个被压缩过的会话,随机挑一条摘要,问「这条从哪来」。
|
|
111
|
-
三十秒内答不出来,门牌就是断的。**
|
|
112
|
-
|
|
113
|
-
### 2. 一门只回指针、不做转述的检索入口
|
|
114
|
-
|
|
115
|
-
转述是二手污染的来源。**能逐字取回,就不要摘要。**
|
|
116
|
-
|
|
117
|
-
动词最少三个:`现在是什么状态` / `搜一个词` / `打开某个指针`。
|
|
118
|
-
|
|
119
|
-
★ 一个细节值得抄:**检索结果里标明证据等级**(一手 / 索引 / 程序)。
|
|
120
|
-
一屏里同时出现三种来源时,用户必须一眼能看出哪一句是原文、哪一句是转述。
|
|
121
|
-
|
|
122
|
-
### 3. 一个自检(哨兵)
|
|
123
|
-
|
|
124
|
-
**判据 ③ 的落地**:它坏的时候,谁会发现?
|
|
125
|
-
|
|
126
|
-
体检项要跑得起来、要能给出分级状态(正常 / 提示 / 警告 / 错误)。
|
|
127
|
-
**没有哨兵的体系,坏了不会有人说。**
|
|
128
|
-
|
|
129
|
-
★ 哨兵最容易漏的一种失败:**它自己坏了。**
|
|
130
|
-
所以哨兵也要能被一个外部动作验证 —— 否则你只是把「没人知道」换成了「一个同样没人知道的东西说没事」。
|
|
131
|
-
|
|
132
|
-
---
|
|
133
|
-
|
|
134
|
-
## 五、观测面 · 一个容易被漏掉的洞
|
|
135
|
-
|
|
136
|
-
**你的每一个执行器都要有对应的观测面。** 一个调好就不再看的阈值系统,
|
|
137
|
-
你永远不会误伤过几次 —— 「断过没有、断错没有」必须有人能答。
|
|
138
|
-
|
|
139
|
-
压缩这一层尤其如此:**压缩不会报错,它只会成功地丢掉东西。**
|
|
140
|
-
所以你要看到的不是「压缩成功 / 失败」,是**「这次压掉了多少、压掉的那部分还能不能找回来」**。
|
|
141
|
-
|
|
142
|
-
---
|
|
143
|
-
|
|
144
|
-
## 六、怎么知道自己已经退化了(三条可执行的检验)
|
|
145
|
-
|
|
146
|
-
这一层的难点在于**它坏的时候不疼**。所以需要主动测,不能等它疼。
|
|
147
|
-
|
|
148
|
-
**检验一 · 门牌抽查**
|
|
149
|
-
拿一个被压缩过的会话,随机挑三条摘要,看能不能通过门牌回到原文。
|
|
150
|
-
一条回不去,链条就是断的。
|
|
151
|
-
|
|
152
|
-
**检验二 · 逐字核对**
|
|
153
|
-
找一条你「记得」的事实,去一手层核对原文。
|
|
154
|
-
如果原文和你记得的不完全一致 —— 不是记错,是压缩时偏了,而且你一直没发现。
|
|
155
|
-
|
|
156
|
-
**检验三 · 三份真相计数**
|
|
157
|
-
同一个事实,你手里有几条来源?
|
|
158
|
-
超过一条,说明权威源没定;这时候你每一次引用都在做一次隐性的仲裁。
|
|
159
|
-
|
|
160
|
-
★ 三条都不需要特殊工具,需要的是**愿意去测**。
|
|
161
|
-
而愿意去测的前提,是你知道自己可能已经坏了 —— 这正是这一节存在的理由。
|
|
1
|
+
# Ⅰ · ECHO —— 生命核心
|
|
2
|
+
|
|
3
|
+
> ★★ **2026-10-23 更新:ECHO 的层编号与判据已由 [`docs/ECHO-权威口径.md`](../docs/ECHO-权威口径.md) 接续。**
|
|
4
|
+
> 那份是唯一权威。这份留着讲「**为什么**要有 ECHO(思考是怎么退化的)」—— 那部分不受影响。
|
|
5
|
+
|
|
6
|
+
**它管空间。** 上下文会满,满了要丢,丢了还要能找回来。
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## 一、思考是怎么退化的(这一节是整份文件的前提)
|
|
11
|
+
|
|
12
|
+
先说清楚要解决的是什么。绝大多数人把它描述成「上下文满了会忘事」——那只是第一句,
|
|
13
|
+
真正致命的在后面。
|
|
14
|
+
|
|
15
|
+
```
|
|
16
|
+
第 1 步 上下文满了
|
|
17
|
+
→ 引擎压缩,把前面几十轮变成一段摘要
|
|
18
|
+
|
|
19
|
+
第 2 步 摘要写得挺准
|
|
20
|
+
→ 「用户要求验证插件安装」「确认了 32 项检查通过」
|
|
21
|
+
→ 每一条都对,没有任何一条是编的
|
|
22
|
+
|
|
23
|
+
第 3 步 但它不带出处
|
|
24
|
+
→ 你读到的是一句「确认了 32 项」,不是「在第 47 轮,verify-live 打了 32/1/0」
|
|
25
|
+
→ 想核对,只能翻回压缩前的全部记录
|
|
26
|
+
|
|
27
|
+
第 4 步 于是你不核对
|
|
28
|
+
→ 因为你「记得」它是对的。注意:这不是懒,这是摘要工作得太好
|
|
29
|
+
|
|
30
|
+
第 5 步 下一轮推理建立在这个「记得」上
|
|
31
|
+
→ 摘要里只要有一个细节偏了半个字,它现在变成了前提
|
|
32
|
+
|
|
33
|
+
第 6 步 偏了的那条继续往下传
|
|
34
|
+
→ 后面的推理每一步都正确,而每一步都踩在偏掉的前提上
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
★ **这就是「思考退化」,它和遗忘是两回事。**
|
|
38
|
+
|
|
39
|
+
遗忘是「想不起来」,你能察觉。
|
|
40
|
+
退化是**「想得起来,但那个记忆已经偏了,而你在它上面继续正确地推理」** —— 察觉不到。
|
|
41
|
+
|
|
42
|
+
一句话:**退化不是变笨,是在错误的前提上继续正确地推理。**
|
|
43
|
+
|
|
44
|
+
所以 ECHO 管的不是「记住」,是**「凭什么相信你记得的那一句」**。
|
|
45
|
+
|
|
46
|
+
---
|
|
47
|
+
|
|
48
|
+
## 二、为什么「装记忆插件」不解决这件事
|
|
49
|
+
|
|
50
|
+
最直觉的解法是装插件:向量库、知识图谱、Memory Spaces。但它们都在**存**这一侧,
|
|
51
|
+
而退化发生在**丢**这一侧。
|
|
52
|
+
|
|
53
|
+
```
|
|
54
|
+
原始记录 ──压缩──▶ 摘要 ──▶ 记忆插件 ──▶ 取回来的是摘要
|
|
55
|
+
↑ ↓
|
|
56
|
+
└──────── 真正的原文在这里,但已经没人指向它 ────────┘
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
你把最好的存储接在摘要后面,取回来的仍然是摘要。
|
|
60
|
+
**压缩发生在存与取之间,而插件管不到那一段。**
|
|
61
|
+
|
|
62
|
+
### 三份真相的陷阱
|
|
63
|
+
|
|
64
|
+
那装三个记忆插件呢?更糟。
|
|
65
|
+
|
|
66
|
+
三个插件各存一份、各有各的摘要策略,于是同一个事实有三条版本。
|
|
67
|
+
**这时候真正的成本不是存储,是对账** —— 每次要引用它,你得先决定信哪一份。
|
|
68
|
+
|
|
69
|
+
**所以第一条判据是「先定唯一权威源」,第二条才是「建机制」。反过来做一定会双重记账。**
|
|
70
|
+
|
|
71
|
+
---
|
|
72
|
+
|
|
73
|
+
## 三、压缩有两条路,要补的是**下面那条**
|
|
74
|
+
|
|
75
|
+
| | 谁在驱动 | 产物带不带指针 |
|
|
76
|
+
|---|---|---|
|
|
77
|
+
| **模型驱动** | agent 自己写摘要 | ✅ 天然带 —— 它知道自己是从哪读的 |
|
|
78
|
+
| **兜底压缩** | 没人响应提示时,引擎**自动出手** | ❌ 通常不带 |
|
|
79
|
+
|
|
80
|
+
**上面那条你不装也有;下面那条你不装就缺。**
|
|
81
|
+
|
|
82
|
+
为什么下一条会缺:那种压缩器的指令通常要求「保留精确路径 / 命令 / 报错原文 / 数值 / 函数签名」——
|
|
83
|
+
**每一条都是「要写得准」,但没有一句要求标注「这话从哪来」。**
|
|
84
|
+
|
|
85
|
+
**于是它写出来的东西每一条都对,每一条都无从核对。**
|
|
86
|
+
|
|
87
|
+
而且失败是无声的:**你不会觉得丢了东西,你只是不再想起它。**
|
|
88
|
+
|
|
89
|
+
**所以「门牌」要补的正是这一条。** 下面三样里,第 1 样就是它。
|
|
90
|
+
|
|
91
|
+
---
|
|
92
|
+
|
|
93
|
+
## 四、机制面 · 至少要有三样
|
|
94
|
+
|
|
95
|
+
### 1. 丢的时候,产物必须带得回原文的指针(门牌)
|
|
96
|
+
|
|
97
|
+
**这是整套东西的兜底承诺。** 没有它,压缩就是单向的遗忘。
|
|
98
|
+
|
|
99
|
+
三级纪律 —— **照抄即可**:
|
|
100
|
+
|
|
101
|
+
```
|
|
102
|
+
① 能拿到精确位置 → 写精确位置
|
|
103
|
+
② 拿不到 → 写「位置未知」+ 容器标识 + 一个检索入口
|
|
104
|
+
③ 绝不猜 —— 错的门牌比没有更坏
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
★ 为什么第 ③ 条单独列出:**一个错的门牌会让你以为能找到,于是不去找。**
|
|
108
|
+
那比「找不到」坏得多。
|
|
109
|
+
|
|
110
|
+
**判据:拿一个被压缩过的会话,随机挑一条摘要,问「这条从哪来」。
|
|
111
|
+
三十秒内答不出来,门牌就是断的。**
|
|
112
|
+
|
|
113
|
+
### 2. 一门只回指针、不做转述的检索入口
|
|
114
|
+
|
|
115
|
+
转述是二手污染的来源。**能逐字取回,就不要摘要。**
|
|
116
|
+
|
|
117
|
+
动词最少三个:`现在是什么状态` / `搜一个词` / `打开某个指针`。
|
|
118
|
+
|
|
119
|
+
★ 一个细节值得抄:**检索结果里标明证据等级**(一手 / 索引 / 程序)。
|
|
120
|
+
一屏里同时出现三种来源时,用户必须一眼能看出哪一句是原文、哪一句是转述。
|
|
121
|
+
|
|
122
|
+
### 3. 一个自检(哨兵)
|
|
123
|
+
|
|
124
|
+
**判据 ③ 的落地**:它坏的时候,谁会发现?
|
|
125
|
+
|
|
126
|
+
体检项要跑得起来、要能给出分级状态(正常 / 提示 / 警告 / 错误)。
|
|
127
|
+
**没有哨兵的体系,坏了不会有人说。**
|
|
128
|
+
|
|
129
|
+
★ 哨兵最容易漏的一种失败:**它自己坏了。**
|
|
130
|
+
所以哨兵也要能被一个外部动作验证 —— 否则你只是把「没人知道」换成了「一个同样没人知道的东西说没事」。
|
|
131
|
+
|
|
132
|
+
---
|
|
133
|
+
|
|
134
|
+
## 五、观测面 · 一个容易被漏掉的洞
|
|
135
|
+
|
|
136
|
+
**你的每一个执行器都要有对应的观测面。** 一个调好就不再看的阈值系统,
|
|
137
|
+
你永远不会误伤过几次 —— 「断过没有、断错没有」必须有人能答。
|
|
138
|
+
|
|
139
|
+
压缩这一层尤其如此:**压缩不会报错,它只会成功地丢掉东西。**
|
|
140
|
+
所以你要看到的不是「压缩成功 / 失败」,是**「这次压掉了多少、压掉的那部分还能不能找回来」**。
|
|
141
|
+
|
|
142
|
+
---
|
|
143
|
+
|
|
144
|
+
## 六、怎么知道自己已经退化了(三条可执行的检验)
|
|
145
|
+
|
|
146
|
+
这一层的难点在于**它坏的时候不疼**。所以需要主动测,不能等它疼。
|
|
147
|
+
|
|
148
|
+
**检验一 · 门牌抽查**
|
|
149
|
+
拿一个被压缩过的会话,随机挑三条摘要,看能不能通过门牌回到原文。
|
|
150
|
+
一条回不去,链条就是断的。
|
|
151
|
+
|
|
152
|
+
**检验二 · 逐字核对**
|
|
153
|
+
找一条你「记得」的事实,去一手层核对原文。
|
|
154
|
+
如果原文和你记得的不完全一致 —— 不是记错,是压缩时偏了,而且你一直没发现。
|
|
155
|
+
|
|
156
|
+
**检验三 · 三份真相计数**
|
|
157
|
+
同一个事实,你手里有几条来源?
|
|
158
|
+
超过一条,说明权威源没定;这时候你每一次引用都在做一次隐性的仲裁。
|
|
159
|
+
|
|
160
|
+
★ 三条都不需要特殊工具,需要的是**愿意去测**。
|
|
161
|
+
而愿意去测的前提,是你知道自己可能已经坏了 —— 这正是这一节存在的理由。
|