File size: 15,665 Bytes
2a55985 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 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 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 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 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 | # Capability 数据基座 — 复用规则 + 自产规则
> **任务来源**:Task #241 (A0)。本文件定义 Capability 框架"哪些数据可以
> 跨 capability 共享 / 谁读谁写 / 怎么自产新基座"。
>
> 配套实施任务:
>
> - **B2** — 已落地外部知识 adapter(`internal-experience` 真读自产 DF)
> - **B7** — 自产 DF distill / reuse / 表 schema(待实施)
---
## 第 1 章 复用规则(精确到表名 + join key + 读/写权限边界)
> **判读原则**:每张表只能有一个**真写入方**(其他 capability 只读)。
> 写权限分散等于失去 source-of-truth,审计时无法回放。
### 1.1 因果网三件套 — `local_causal_nets` / `causal_edges` / `literature_buckets` / `causal_buckets`
| 表 | 真写入方 | 读权限 | join key | 备注 |
|---|---|---|---|---|
| `local_causal_nets` | research-engine M3 (`pipelines/runners.py:M3`) | 任何 capability | `paper_id` | per-paper 因果图,M3 fulltext_upgrade 落地 |
| `causal_edges` | research-engine M3 + M5 | 任何 capability | `(source_node_id, target_node_id, paper_id)` | 边级原始 |
| `literature_buckets` | research-engine M5 (`networks/causal.py:215-217`) | 任何 capability | `bucket_key k(ε)` | bucket 聚合 |
| `causal_buckets` | research-engine M5 | 任何 capability | `bucket_key` | 同上,因果系数 + CI |
- **写入闸门**:M3/M5 跑完一份 paper 才落库,**不允许 capability 直接 INSERT**。
写入路径冻结在 `docs/architecture/causal-network.md`。
- **对照例(target_discovery)**:走 `causal_buckets` LEFT JOIN
`disease_target_mapping` 得到"该疾病有多少 paper 支持该靶点"。
### 1.2 药物网四件套 — `drug_nodes` / `target_nodes` / `outcome_nodes` / `*_edges`
| 表 | 真写入方 | 读权限 | join key | 备注 |
|---|---|---|---|---|
| `drug_nodes` | `PostgresDrugNetwork.bootstrap` (`drug_store.py:29-595`) | 任何 capability | `drug_id` | 2336 drugs(实测) |
| `target_nodes` | 同上 | 同上 | `target_id` | |
| `outcome_nodes` | 同上 | 同上 | `outcome_id` | |
| 三类边 (`drug_target_edges` / `drug_outcome_edges` / `trial_edges`) | 同上 | 同上 | 复合 key | 6069 trials(实测) |
- **写入闸门**:`drug_trial_mapping.csv` 是唯一来源,bootstrap 只跑一次/启动。
- **对照例(ligand_screening)**:走 `target_nodes` ∩ `drug_target_edges` 找
已知 active ligand 作为 query expansion;**不允许往 `drug_nodes` 写
syntheticActive**(这正是 CONT-001 已封印的欺骗路径)。
### 1.3 DuckDB 文献索引 + 文献 snapshot
| 资源 | 真写入方 | 读权限 | 备注 |
|---|---|---|---|
| `papers.duckdb`(snapshot) | Dataset Download workflow(本地手工/CI) | 任何 capability(只读 ATTACH) | mtime 变 → BM25 index 重建 |
| `fts_main_papers` index | `tool_handlers.py:_fts_get_or_build` lazy 建 | 同上 | in-memory,per-process |
| `pmid_paper_lookup` | research-engine 写,容器启动初始化 | 任何 capability | PMID → paper_id 反查 |
- **写入闸门**:snapshot 文件由独立的 dataset workflow 生成,read-only ATTACH;
capability 跑 inner loop 时**不允许写** `papers.duckdb`。
- 真读路径 `external-knowledge/literature.adapter.ts`(三路 fallback,
origin=`'duckdb:bm25'` / `'duckdb:cooccur'` / `'epmc'`)。
### 1.4 `submission_feedback_ledger` — 主办方真值回灌(已闭环)
| 表 | 真写入方 | 读权限 | join key | 备注 |
|---|---|---|---|---|
| `submission_feedback_ledger` | `routes/competition-feedback-import.ts`(操作员上传/HMAC bridge 同步) | reviewer.fitness | `(submission_id, metric_name)` | metric: ef1 / ef5 / auc;value 已归一到 [0,1] |
- **闭环路径**(diagnosis 1.2 第 6 通道):reviewer `external_truth` 通道
对每行 metric 做 LEFT JOIN,有则升级到 `with-truth` 权重套,无则 fallback
`truthIsFallback=true`。
- **对照例(admet_prediction)**:实验测得的 PK 数据回灌走同一表,
`metric_name` 用 `clearance` / `t_half`,reviewer 同样 LEFT JOIN。
### 1.5 `user_memory_facts` — 跨 session 用户经验
| 表 | 真写入方 | 读权限 | join key | 备注 |
|---|---|---|---|---|
| `user_memory_facts` | `routes/messages.ts` (chat 端 distill) | 任何 capability(限当前 user) | `(user_id, fact_kind)` | 跨 session 长期记忆 |
- **写入闸门**:只在 chat 流程显式 `remember_fact` 调用时落库;**capability
不直接写**(避免不同 capability 写入风格冲突)。
- 读权限:每个 capability 在 `observe` 步可以拿当前 user 的 facts 做 prompt
增强,但**不允许跨 user 读**。
### 1.6 复用矩阵速查
| 表 / 资源 | ligand_screening | target_discovery | admet_prediction | meta_planner |
|---|---|---|---|---|
| `causal_*` | 读(target→outcome 路径) | 读(disease→target 证据) | 读(drug→outcome 副作用) | 读(理由摘要) |
| `drug_*` | 读 + 用 query expansion | 读(已知干预) | 读(已知 PK) | 读 |
| `papers.duckdb` | 读(target literature) | 读(disease mechanism) | 读(model literature) | 读 |
| `submission_feedback_ledger` | 读(EF1% 回灌) | 读(target hit rate 回灌) | 读(PK 测试值回灌) | 读(整体 thumb-up 回灌) |
| `user_memory_facts` | 读(用户偏好) | 同 | 同 | 读 + 路由偏好 |
| `capability_data_foundation`(B7) | 读 + 写 | 读 + 写 | 读 + 写 | 读 |
---
## 第 2 章 自产规则(B7 实施依据)
> 第 1 章是"已有的复用基座",本章是"capability 跑完一轮后能产出什么、
> 怎么落库、谁能读"。
### 2.1 命名空间约定
- `<capability_id>_<entity_kind>` — 每条 distill row 必须能反查 capability
source。
- 例:
- `cap_ligand_screening_drugclip / param-fingerprint`
- `cap_target_discovery_omics / disease-target-prior`
- `cap_admet_prediction_qsar / model-error-pattern`
### 2.2 表 schema 草案 — `capability_data_foundation`
```sql
CREATE TABLE capability_data_foundation (
id VARCHAR PRIMARY KEY DEFAULT gen_random_uuid(),
capability_id VARCHAR NOT NULL, -- 写入方,FK to capabilities.id
kind VARCHAR NOT NULL, -- 命名空间内 entity_kind
payload JSONB NOT NULL, -- 实际内容,schema 由 kind 决定(见第 3 章)
payload_schema_version INTEGER NOT NULL DEFAULT 1, -- payload schema 版本号,B7 演进时 +1
produced_at TIMESTAMPTZ NOT NULL DEFAULT now(),
source_run_id VARCHAR NOT NULL, -- FK to capability_run_audit.run_id
source_version VARCHAR NOT NULL, -- 产出时的 active_version_id,审计可回放
retention_days INTEGER NOT NULL DEFAULT 90, -- TTL,过期由 cleanup job 删
is_failure_distill BOOLEAN NOT NULL DEFAULT false, -- true 表示从失败路径 distill
CONSTRAINT cap_df_kind_payload_check CHECK (jsonb_typeof(payload) = 'object'),
CONSTRAINT cap_df_capability_kind_idx UNIQUE (capability_id, kind, source_run_id)
);
CREATE INDEX cap_df_lookup ON capability_data_foundation (capability_id, kind, produced_at DESC);
CREATE INDEX cap_df_source_run ON capability_data_foundation (source_run_id);
```
**关键约束**:
- `payload_schema_version` 必填 — 每个 `kind` 的 payload 演进时整体 +1,
B2 `internal-experience.adapter.ts` 读时按 version 适配。
- `source_run_id` + `source_version` 必填 — 任何 distill 都能回放到产出
时的 capability 配置。
- `is_failure_distill` — 失败路径也能 distill(见第 2.4 节)。
### 2.3 Distill 时机(成功路径)
每次 cap.run 完成后(无论 promote 与否),走以下决策:
```
if (run.reviewerScore > capability.distillThreshold):
write_distill(kind='param-fingerprint', payload=run.config_summary)
if (run.has_novel_evidence): # external_truth 通道有新真值回灌
write_distill(kind='evidence-bucket-prior', payload=run.evidence_summary)
```
- **distillThreshold** 默认 0.7(每 capability 在 evolution_config 可覆盖)。
- distill 是 **fire-and-forget**,失败不阻塞 cap.run。
### 2.4 Distill 时机(失败路径)⭐
失败的 cap.run **同样有学习价值** — 失败模式是后续避坑的元知识:
```
if (run.judge.fallback_rate > 0.5):
write_distill(kind='failure-pattern',
payload={ failed_channels, root_cause_diagnosis, retry_advice },
is_failure_distill=true)
if (run.quarantine_hits.length > 0):
write_distill(kind='quarantine-trigger',
payload={ contamination_ids, run_input_fingerprint },
is_failure_distill=true)
```
后续同伴 capability 通过 `internal-experience` adapter 检索时,**默认不
返失败 distill**;只有在 `kind` 显式包含 `failure-pattern` 或 `evidence
.bias.confidence > 0.7` 时才返。
### 2.5 同伴 capability 通过 `internal-experience` adapter 读取
`external-knowledge/internal-experience.adapter.ts` 已在 B2 真接:
```ts
queryExternalKnowledge({
kind: 'internal_experience',
capabilityId: 'cap_target_discovery_omics_v1',
query: { kind: 'disease-target-prior', filters: { disease: 'Alzheimer' } },
})
// → KnowledgeResult[]
// 内部走:SELECT payload FROM capability_data_foundation
// WHERE kind = $1 AND payload @> $2
// AND produced_at > now() - interval '90 days'
// ORDER BY produced_at DESC LIMIT 20
```
**关键约束**:
- 读其他 capability 的自产 DF **不触发 budget guard**(因为是自家系统,
不是外部 API);但**写自产 DF 的 distill 调用受 distill_budget 限制**
(默认每小时每 capability 100 条)。
- `excludedSelf` 默认 true:同一 capability 不读自己的 distill(避免回声
室)— `excludedSelf=false` 仅在 `meta_planner` 复用历史路由决策时启用。
---
## 第 3 章 示例 — 2 个 capability 的具体自产 schema
### 3.1 ligand_screening:`kind='param-fingerprint'`
```ts
interface ParamFingerprintPayload {
pipeline_version: string; // 产出时的 pipeline_skeleton 版本
config: {
shingleK: number;
topK: number;
fingerprintBackend: 'jaccard' | 'rdkit_morgan'; // B8 后才有 rdkit_morgan
rerank: boolean;
};
outcome_metrics: {
ef1: number | null; // null 表示未计算(无 actives 标注)
ef5: number | null;
auc_roc: number | null;
reviewer_score: number; // 0..1
};
novel_actives: Array<{ // 跑出来的 top-K ligand 中,以前没遇到过的
smiles: string;
rank: number;
score: number;
}>;
}
```
- **复用场景**:同一 capability 下次 cold_start 时 `cap.observe` 步从这里
拉历史最优 config 当 prior;`meta_planner` fan-out 时按
`outcome_metrics.ef1 desc` 选最近 7 天表现最好的 capability 实例。
### 3.2 ligand_screening:`kind='failure-pattern'`(失败 distill)
```ts
interface FailurePatternPayload {
failed_channels: string[]; // 例: ['intent_coverage', 'traceability']
root_cause_diagnosis: string; // 由 diagnoser 角色 LLM 产出(B6)
retry_advice: {
config_changes: Record<string, unknown>; // 建议改的 config
avoid_inputs: string[]; // 例: ["candidate_count > 10000"]
};
contamination_hits: string[]; // CONT-XXX 列表
}
```
### 3.3 target_discovery:`kind='disease-target-prior'`
```ts
interface DiseaseTargetPriorPayload {
disease_term: string; // 例: 'Alzheimer disease'
disease_mesh_id: string | null; // 标准化标识
candidate_targets: Array<{
target_id: string;
target_symbol: string;
evidence_score: number; // 0..1,综合 causal + literature
evidence_sources: {
causal_bucket_count: number;
paper_count: number;
drug_intervention_count: number;
};
novelty_flag: boolean; // 是否为新候选(不在已知 disease-target 库里)
}>;
generated_at: string; // ISO 时间戳
}
```
- **复用场景**:`cap_ligand_screening_drugclip` 在 plan 步可读这个,直接
用 `candidate_targets[0].target_id` 当 query expansion;
`cap_admet_prediction` 在 observe 步读这个,过滤掉 novelty_flag=true 的
靶点(实验数据少)。
### 3.4 target_discovery:`kind='evidence-bucket-prior'`
```ts
interface EvidenceBucketPriorPayload {
bucket_key: string; // 来自 causal_buckets,k(ε)
source_node: { id: string, label: string };
target_node: { id: string, label: string };
pooled_estimate: number; // 因果效应 pooled
ci: { lower: number, upper: number };
paper_evidence_count: number;
validation_status: 'unvalidated' | 'validated_v2' | 'rejected_v2';
}
```
### 3.5 冷启动避坑 — 第一次跑时 DF 是空的,如何避免循环依赖
**问题**:Capability 第一次进入 `COLD_START`,`capability_data_foundation`
里没有任何 row;`internal-experience` adapter 返空数组,inner loop 的
`observe` 步拿不到 prior。
**解决方案**(B7 必须实施):
1. **空查询返显式 marker**:`internal-experience.adapter.ts` 在 0 行时
返 `{ origin: 'internal:empty', results: [] }`,**不走 cache 路径**,
inner loop 看到 `origin='internal:empty'` 就走"无 prior"分支。
2. **跨 capability seeding**:新 capability 注册时,操作员可以手工指定
`seed_from_capabilities: string[]` — cold_start 阶段允许从兄弟 capability
的 `param-fingerprint` 拉 N=10 条作为冷启 prior(只读,不写自家 DF)。
这条路径需要在 `capabilities.config.cold_start_seeds` 显式开启,默认关。
3. **第一轮 100% 走外部 knowledge**:cold_start 阶段 router 强制把
`internal_experience` adapter 的预算设为 0,所有候选生成都依赖
literature / causal / drug 三个真后端 + 操作员指定的 seed 数据。
4. **graduate 后再开自产读**:`COLD_START → GRADUATED` 转换条件之一是
"至少产出 30 条 `param-fingerprint`"(不强求全部高分,只要有数据)。
GRADUATED 后 `internal_experience` 预算回到默认,自产 DF 进入复用。
---
## 第 4 章 表 schema 落地清单(B7 工作项)
下表是 B7 真实施时需要 db:push 的 schema 改动清单:
| 表 / 字段 | 类型 | 备注 |
|---|---|---|
| `capability_data_foundation` (整表) | 见 2.2 | 新建,带 4 个 index |
| `capabilities.config.distillThreshold` | jsonb 字段内 number | 默认 0.7 |
| `capabilities.config.distill_budget_per_hour` | jsonb 字段内 number | 默认 100 |
| `capabilities.config.cold_start_seeds` | jsonb 字段内 string[] | 默认 `[]` |
| `capability_run_audit.distilled_count` | integer | 本次 run distill 出的条数 |
| cleanup cron `cap_df_ttl_cleanup` | sql job | 删 `produced_at < now() - retention_days` 的 row |
**注意 ID 安全**:`capability_data_foundation.id` 用 `varchar` + UUID 默认值
(对照本 repo 既有 `varchar` 主键风格,见 `lib/quarantine/index.ts` 的
`newId('qhit')` 风格)。**不要用 serial**(参见 important_database_safety_rules)。
---
## 第 5 章 一句话结论
1. **5 类已有数据基座**(因果网 / 药物网 / DuckDB 文献 /
submission_feedback_ledger / user_memory_facts)分别有**唯一真写入方**,
capability 只读不写。
2. **自产新基座**走唯一表 `capability_data_foundation`,带 capability_id +
kind + payload jsonb + retention_days。
3. **失败路径也 distill**(`is_failure_distill=true`),失败模式是元知识。
4. **冷启动必须有显式空 marker**,`internal:empty` origin 让 inner loop
走"无 prior"分支,避免兜底假装有数据。
5. **跨 capability seeding 默认关**;开了之后 cold_start 阶段允许从兄弟
capability 的 param-fingerprint 拉 N=10 条。
|